ctxloom-pro 1.3.1 → 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 +2 -2
- package/apps/dashboard/dist/server/index.js +260 -60
- package/dist/{chunk-TIYTPWYN.js → chunk-J2NLNQ4I.js} +1851 -143
- package/dist/index.js +44 -8
- package/dist/{src-KTFHRVTO.js → src-PVBWVQMM.js} +54 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ The full first-run flow is **one install + one trial + one init per project.** E
|
|
|
47
47
|
npm install -g ctxloom-pro
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
> **For local trial / dev use the unpinned command above is fine.** For unattended CI usage, pin to the exact version (`ctxloom-pro@1.
|
|
50
|
+
> **For local trial / dev use the unpinned command above is fine.** For unattended CI usage, pin to the exact version (`ctxloom-pro@1.5.0`) so future CLI releases don't silently desync your agent-spec coverage — see the workflow example below.
|
|
51
51
|
|
|
52
52
|
### 2 — Start your free trial (once per email)
|
|
53
53
|
|
|
@@ -346,7 +346,7 @@ jobs:
|
|
|
346
346
|
# Exact pin (not `@^1`) so future CLI releases that add/remove MCP
|
|
347
347
|
# tools don't silently desync your reviewer-agent specs. Bump on
|
|
348
348
|
# every release; see CHANGELOG.md for the live version table.
|
|
349
|
-
- run: npm install -g ctxloom-pro@1.
|
|
349
|
+
- run: npm install -g ctxloom-pro@1.5.0
|
|
350
350
|
- run: ctxloom index
|
|
351
351
|
- run: ctxloom rules check --json
|
|
352
352
|
```
|
|
@@ -166,12 +166,12 @@ var init_VectorStore = __esm({
|
|
|
166
166
|
// server/index.ts
|
|
167
167
|
import express from "express";
|
|
168
168
|
import cors from "cors";
|
|
169
|
-
import
|
|
170
|
-
import
|
|
169
|
+
import path46 from "path";
|
|
170
|
+
import fs34 from "fs";
|
|
171
171
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
172
172
|
|
|
173
173
|
// server/loader.ts
|
|
174
|
-
import
|
|
174
|
+
import path40 from "path";
|
|
175
175
|
|
|
176
176
|
// ../../packages/core/src/graph/DependencyGraph.ts
|
|
177
177
|
import fs7 from "fs";
|
|
@@ -3181,8 +3181,8 @@ var CoChangeIndex = class _CoChangeIndex {
|
|
|
3181
3181
|
if (event.isBulk || event.isMerge) return;
|
|
3182
3182
|
const paths = event.files.map((f) => f.path);
|
|
3183
3183
|
if (paths.length === 0) return;
|
|
3184
|
-
for (const
|
|
3185
|
-
this.nodeCounts.set(
|
|
3184
|
+
for (const path47 of paths) {
|
|
3185
|
+
this.nodeCounts.set(path47, (this.nodeCounts.get(path47) ?? 0) + 1);
|
|
3186
3186
|
}
|
|
3187
3187
|
for (let i = 0; i < paths.length; i++) {
|
|
3188
3188
|
for (let j = i + 1; j < paths.length; j++) {
|
|
@@ -3329,8 +3329,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3329
3329
|
*/
|
|
3330
3330
|
snapshot() {
|
|
3331
3331
|
const nodes = {};
|
|
3332
|
-
for (const [
|
|
3333
|
-
nodes[
|
|
3332
|
+
for (const [path47, raw] of this.nodes) {
|
|
3333
|
+
nodes[path47] = {
|
|
3334
3334
|
commits: raw.commits,
|
|
3335
3335
|
churnLines: raw.churnLines,
|
|
3336
3336
|
bugCommits: raw.bugCommits,
|
|
@@ -3345,8 +3345,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3345
3345
|
*/
|
|
3346
3346
|
static load(s) {
|
|
3347
3347
|
const idx = new _ChurnIndex();
|
|
3348
|
-
for (const [
|
|
3349
|
-
idx.nodes.set(
|
|
3348
|
+
for (const [path47, raw] of Object.entries(s.nodes)) {
|
|
3349
|
+
idx.nodes.set(path47, {
|
|
3350
3350
|
commits: raw.commits,
|
|
3351
3351
|
churnLines: raw.churnLines,
|
|
3352
3352
|
bugCommits: raw.bugCommits,
|
|
@@ -3359,8 +3359,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3359
3359
|
// -------------------------------------------------------------------------
|
|
3360
3360
|
// Private helpers
|
|
3361
3361
|
// -------------------------------------------------------------------------
|
|
3362
|
-
getOrCreate(
|
|
3363
|
-
const existing = this.nodes.get(
|
|
3362
|
+
getOrCreate(path47) {
|
|
3363
|
+
const existing = this.nodes.get(path47);
|
|
3364
3364
|
if (existing !== void 0) return existing;
|
|
3365
3365
|
const fresh = {
|
|
3366
3366
|
commits: 0,
|
|
@@ -3369,7 +3369,7 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3369
3369
|
authorCounts: {},
|
|
3370
3370
|
lastTouch: 0
|
|
3371
3371
|
};
|
|
3372
|
-
this.nodes.set(
|
|
3372
|
+
this.nodes.set(path47, fresh);
|
|
3373
3373
|
return fresh;
|
|
3374
3374
|
}
|
|
3375
3375
|
};
|
|
@@ -3450,12 +3450,12 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3450
3450
|
*/
|
|
3451
3451
|
snapshot() {
|
|
3452
3452
|
const nodes = {};
|
|
3453
|
-
for (const [
|
|
3453
|
+
for (const [path47, raw] of this.nodes) {
|
|
3454
3454
|
const authorWeights = {};
|
|
3455
3455
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3456
3456
|
authorWeights[email] = { ...entry };
|
|
3457
3457
|
}
|
|
3458
|
-
nodes[
|
|
3458
|
+
nodes[path47] = { authorWeights, lastTouch: raw.lastTouch };
|
|
3459
3459
|
}
|
|
3460
3460
|
return { version: 1, nodes };
|
|
3461
3461
|
}
|
|
@@ -3464,23 +3464,23 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3464
3464
|
*/
|
|
3465
3465
|
static load(s) {
|
|
3466
3466
|
const idx = new _OwnershipIndex();
|
|
3467
|
-
for (const [
|
|
3467
|
+
for (const [path47, raw] of Object.entries(s.nodes)) {
|
|
3468
3468
|
const authorWeights = {};
|
|
3469
3469
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3470
3470
|
authorWeights[email] = { ...entry };
|
|
3471
3471
|
}
|
|
3472
|
-
idx.nodes.set(
|
|
3472
|
+
idx.nodes.set(path47, { authorWeights, lastTouch: raw.lastTouch });
|
|
3473
3473
|
}
|
|
3474
3474
|
return idx;
|
|
3475
3475
|
}
|
|
3476
3476
|
// -------------------------------------------------------------------------
|
|
3477
3477
|
// Private helpers
|
|
3478
3478
|
// -------------------------------------------------------------------------
|
|
3479
|
-
getOrCreate(
|
|
3480
|
-
const existing = this.nodes.get(
|
|
3479
|
+
getOrCreate(path47) {
|
|
3480
|
+
const existing = this.nodes.get(path47);
|
|
3481
3481
|
if (existing !== void 0) return existing;
|
|
3482
3482
|
const fresh = { authorWeights: {}, lastTouch: 0 };
|
|
3483
|
-
this.nodes.set(
|
|
3483
|
+
this.nodes.set(path47, fresh);
|
|
3484
3484
|
return fresh;
|
|
3485
3485
|
}
|
|
3486
3486
|
};
|
|
@@ -4718,8 +4718,8 @@ function getErrorMap() {
|
|
|
4718
4718
|
|
|
4719
4719
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
4720
4720
|
var makeIssue = (params) => {
|
|
4721
|
-
const { data, path:
|
|
4722
|
-
const fullPath = [...
|
|
4721
|
+
const { data, path: path47, errorMaps, issueData } = params;
|
|
4722
|
+
const fullPath = [...path47, ...issueData.path || []];
|
|
4723
4723
|
const fullIssue = {
|
|
4724
4724
|
...issueData,
|
|
4725
4725
|
path: fullPath
|
|
@@ -4835,11 +4835,11 @@ var errorUtil;
|
|
|
4835
4835
|
|
|
4836
4836
|
// ../../node_modules/zod/v3/types.js
|
|
4837
4837
|
var ParseInputLazyPath = class {
|
|
4838
|
-
constructor(parent, value,
|
|
4838
|
+
constructor(parent, value, path47, key) {
|
|
4839
4839
|
this._cachedPath = [];
|
|
4840
4840
|
this.parent = parent;
|
|
4841
4841
|
this.data = value;
|
|
4842
|
-
this._path =
|
|
4842
|
+
this._path = path47;
|
|
4843
4843
|
this._key = key;
|
|
4844
4844
|
}
|
|
4845
4845
|
get path() {
|
|
@@ -8288,9 +8288,6 @@ var ProjectRootField = external_exports.string().optional().describe(PROJECT_ROO
|
|
|
8288
8288
|
// ../../packages/core/src/tools/registry.ts
|
|
8289
8289
|
init_logger();
|
|
8290
8290
|
|
|
8291
|
-
// ../../packages/core/src/tools/search.ts
|
|
8292
|
-
init_embedder();
|
|
8293
|
-
|
|
8294
8291
|
// ../../packages/core/src/budget/budget.ts
|
|
8295
8292
|
init_logger();
|
|
8296
8293
|
|
|
@@ -8301,7 +8298,21 @@ import os2 from "os";
|
|
|
8301
8298
|
import path16 from "path";
|
|
8302
8299
|
var DEFAULT_TELEMETRY_DIR = path16.join(os2.homedir(), ".ctxloom", "telemetry");
|
|
8303
8300
|
|
|
8301
|
+
// ../../packages/core/src/budget/learnedSuggestions.ts
|
|
8302
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
8303
|
+
|
|
8304
|
+
// ../../packages/core/src/budget/taskBudget.ts
|
|
8305
|
+
var OVER_BUDGET_ARG_OVERRIDES = Object.freeze({
|
|
8306
|
+
// Budget-surface tools (the 12 source-returning ones).
|
|
8307
|
+
max_response_tokens: 200,
|
|
8308
|
+
response_format: "skeleton",
|
|
8309
|
+
on_budget_exceeded: "skeleton",
|
|
8310
|
+
// Tools with detail_level (hub_nodes, bridge_nodes, etc).
|
|
8311
|
+
detail_level: "minimal"
|
|
8312
|
+
});
|
|
8313
|
+
|
|
8304
8314
|
// ../../packages/core/src/tools/search.ts
|
|
8315
|
+
init_embedder();
|
|
8305
8316
|
var Schema = external_exports.object({
|
|
8306
8317
|
query: external_exports.string().describe("Search query \u2014 natural language or code fragment"),
|
|
8307
8318
|
limit: external_exports.number().max(100).optional().default(10).describe("Maximum results to return"),
|
|
@@ -11318,6 +11329,18 @@ var Schema29 = external_exports.object({
|
|
|
11318
11329
|
project_root: ProjectRootField
|
|
11319
11330
|
});
|
|
11320
11331
|
|
|
11332
|
+
// ../../packages/core/src/tools/minimal-context.ts
|
|
11333
|
+
import { execSync } from "child_process";
|
|
11334
|
+
var Schema30 = external_exports.object({
|
|
11335
|
+
task: external_exports.string().max(200).optional().describe(
|
|
11336
|
+
"Free-text description of what you're about to do (e.g. 'review PR 142', 'rename emitTelemetry'). The tool routes by regex to the most-fitting suggested-first-tool. Capped at 200 chars; control characters stripped."
|
|
11337
|
+
),
|
|
11338
|
+
project_root: ProjectRootField,
|
|
11339
|
+
max_response_tokens: external_exports.number().int().positive().optional(),
|
|
11340
|
+
on_budget_exceeded: external_exports.enum(["skeleton", "truncate", "error"]).optional(),
|
|
11341
|
+
response_format: external_exports.enum(["full", "skeleton", "auto"]).optional()
|
|
11342
|
+
});
|
|
11343
|
+
|
|
11321
11344
|
// ../../packages/core/src/tools/ruleManager.ts
|
|
11322
11345
|
import fs25 from "fs";
|
|
11323
11346
|
import path27 from "path";
|
|
@@ -11429,7 +11452,7 @@ function resolveTelemetryLevel() {
|
|
|
11429
11452
|
}
|
|
11430
11453
|
var TELEMETRY_LEVEL = resolveTelemetryLevel();
|
|
11431
11454
|
var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
11432
|
-
var CTXLOOM_VERSION = "1.
|
|
11455
|
+
var CTXLOOM_VERSION = "1.5.0".length > 0 ? "1.5.0" : "dev";
|
|
11433
11456
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
11434
11457
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
11435
11458
|
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
@@ -11584,9 +11607,186 @@ init_logger();
|
|
|
11584
11607
|
import fs30 from "fs";
|
|
11585
11608
|
import path38 from "path";
|
|
11586
11609
|
|
|
11610
|
+
// ../../packages/core/src/install/installer.ts
|
|
11611
|
+
import fs31 from "fs";
|
|
11612
|
+
import path39 from "path";
|
|
11613
|
+
|
|
11614
|
+
// ../../packages/core/src/install/templates.ts
|
|
11615
|
+
var RULES_BLOCK_CONTENT = `## MCP Tools: ctxloom
|
|
11616
|
+
|
|
11617
|
+
**IMPORTANT: This project has a knowledge graph. ALWAYS use the
|
|
11618
|
+
ctxloom MCP tools BEFORE Grep/Glob/Read to explore the codebase.**
|
|
11619
|
+
The graph is faster, cheaper (fewer tokens), and gives you
|
|
11620
|
+
structural context (callers, dependents, test coverage) that file
|
|
11621
|
+
scanning cannot.
|
|
11622
|
+
|
|
11623
|
+
### Start every workflow with \`ctx_get_minimal_context\`
|
|
11624
|
+
|
|
11625
|
+
The first MCP call into ctxloom should always be
|
|
11626
|
+
\`ctx_get_minimal_context(task="<what you're about to do>")\`. It
|
|
11627
|
+
returns ~150 tokens of orientation plus a task-aware
|
|
11628
|
+
\`suggested_first_tool\` you should call next instead of guessing.
|
|
11629
|
+
|
|
11630
|
+
### When to use graph tools FIRST
|
|
11631
|
+
|
|
11632
|
+
- **Code review**: \`ctx_detect_changes\` + \`ctx_get_review_context\`
|
|
11633
|
+
instead of reading whole files
|
|
11634
|
+
- **Understanding impact**: \`ctx_blast_radius\` + \`ctx_get_affected_flows\`
|
|
11635
|
+
instead of manually tracing imports
|
|
11636
|
+
- **Refactor planning**: \`ctx_get_call_graph(direction: 'callers')\`
|
|
11637
|
+
+ \`ctx_refactor_preview\` before any rename
|
|
11638
|
+
- **Architecture questions**: \`ctx_architecture_overview\`,
|
|
11639
|
+
\`ctx_community_list\`, \`ctx_hub_nodes\`
|
|
11640
|
+
- **Finding code**: \`ctx_search\` or \`ctx_full_text_search\` instead
|
|
11641
|
+
of \`Grep\`
|
|
11642
|
+
|
|
11643
|
+
Fall back to Grep/Glob/Read **only** when the graph doesn't cover
|
|
11644
|
+
what you need.
|
|
11645
|
+
|
|
11646
|
+
### Follow the \`next_tool_suggestions\` in every response
|
|
11647
|
+
|
|
11648
|
+
Every budget-wrapped ctxloom response includes
|
|
11649
|
+
\`meta.next_tool_suggestions\` \u2014 author-curated follow-ups with
|
|
11650
|
+
\`why\` reasoning and \`estimated_tokens\` per entry. Pick from
|
|
11651
|
+
those instead of guessing.
|
|
11652
|
+
|
|
11653
|
+
### Token-budget protocol
|
|
11654
|
+
|
|
11655
|
+
- Target: \u22648 tool calls per task, \u22642000 total tokens of graph context
|
|
11656
|
+
- Pass \`max_response_tokens\` on calls that might return large
|
|
11657
|
+
responses; the budget surface returns skeletons instead of dumping
|
|
11658
|
+
- Use \`response_format: 'skeleton'\` when you know you only need
|
|
11659
|
+
signatures, not bodies
|
|
11660
|
+
|
|
11661
|
+
### Key tools at a glance
|
|
11662
|
+
|
|
11663
|
+
| Tool | Use when |
|
|
11664
|
+
|------|----------|
|
|
11665
|
+
| \`ctx_get_minimal_context\` | START HERE \u2014 orientation anchor |
|
|
11666
|
+
| \`ctx_detect_changes\` | Reviewing code changes; risk-scored |
|
|
11667
|
+
| \`ctx_get_review_context\` | Token-efficient review snippets |
|
|
11668
|
+
| \`ctx_blast_radius\` | Blast radius of a change |
|
|
11669
|
+
| \`ctx_get_affected_flows\` | Execution paths impacted |
|
|
11670
|
+
| \`ctx_get_call_graph\` | Callers / callees of a symbol |
|
|
11671
|
+
| \`ctx_search\` / \`ctx_full_text_search\` | Find code |
|
|
11672
|
+
| \`ctx_architecture_overview\` | High-level codebase map |
|
|
11673
|
+
| \`ctx_refactor_preview\` / \`ctx_apply_refactor\` | Plan a rename |
|
|
11674
|
+
|
|
11675
|
+
### Hooks keep the graph fresh
|
|
11676
|
+
|
|
11677
|
+
\`ctxloom init\` installed a PostToolUse hook on \`Write|Edit\` that
|
|
11678
|
+
runs \`ctxloom update --incremental --quiet\` \u2014 so the graph is
|
|
11679
|
+
always up to date when you query it. No "did the index update yet?"
|
|
11680
|
+
guessing.`;
|
|
11681
|
+
var SESSION_START_HEADER = `#!/usr/bin/env bash
|
|
11682
|
+
# ctxloom \u2014 agent-harness session-start hook
|
|
11683
|
+
# Generated by \`ctxloom init\`. Re-run \`ctxloom init\` to update.
|
|
11684
|
+
# Manual edits will be overwritten on the next install.
|
|
11685
|
+
|
|
11686
|
+
set -e
|
|
11687
|
+
`;
|
|
11688
|
+
var SESSION_START_BODY = `DB=".ctxloom/graph.db"
|
|
11689
|
+
|
|
11690
|
+
if [ -f "$DB" ]; then
|
|
11691
|
+
# \`ctxloom status --json\` is cached + sub-100ms \u2014 keeps the hook
|
|
11692
|
+
# under its 2s timeout even on cold disk.
|
|
11693
|
+
STATS=$(ctxloom status --json 2>/dev/null || echo '{"nodes":0,"edges":0}')
|
|
11694
|
+
NODES=$(echo "$STATS" | grep -oE '"nodes":\\s*[0-9]+' | grep -oE '[0-9]+' || echo "?")
|
|
11695
|
+
EDGES=$(echo "$STATS" | grep -oE '"edges":\\s*[0-9]+' | grep -oE '[0-9]+' || echo "?")
|
|
11696
|
+
|
|
11697
|
+
cat <<EOF
|
|
11698
|
+
[ctxloom] Knowledge graph ready (\${NODES} nodes, \${EDGES} edges).
|
|
11699
|
+
|
|
11700
|
+
Start every workflow with \\\`ctx_get_minimal_context(task="...")\\\`.
|
|
11701
|
+
It returns ~150 tokens of orientation + a task-aware
|
|
11702
|
+
suggested_first_tool. Follow the meta.next_tool_suggestions on
|
|
11703
|
+
every response.
|
|
11704
|
+
|
|
11705
|
+
Prefer ctxloom MCP tools over Grep/Glob/Read:
|
|
11706
|
+
- ctx_detect_changes for code review
|
|
11707
|
+
- ctx_blast_radius / ctx_get_call_graph before refactoring
|
|
11708
|
+
- ctx_architecture_overview for orientation
|
|
11709
|
+
EOF
|
|
11710
|
+
else
|
|
11711
|
+
cat <<EOF
|
|
11712
|
+
[ctxloom] No knowledge graph found here.
|
|
11713
|
+
Run: ctxloom build
|
|
11714
|
+
|
|
11715
|
+
Then restart this session to enable graph-powered queries.
|
|
11716
|
+
EOF
|
|
11717
|
+
fi
|
|
11718
|
+
`;
|
|
11719
|
+
var SESSION_START_FULL = SESSION_START_HEADER + "\n" + SESSION_START_BODY;
|
|
11720
|
+
function plainBody() {
|
|
11721
|
+
return RULES_BLOCK_CONTENT.replace(/^##\s+/gm, "").replace(/^###\s+/gm, "").replace(/^####\s+/gm, "").replace(/\*\*(.*?)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1");
|
|
11722
|
+
}
|
|
11723
|
+
var CURSOR_HEADER = `# ctxloom \u2014 Cursor agent rules
|
|
11724
|
+
# Generated by \`ctxloom init --host=cursor\`. Re-run to update.
|
|
11725
|
+
|
|
11726
|
+
`;
|
|
11727
|
+
var AIDER_HEADER = `# Project Conventions
|
|
11728
|
+
|
|
11729
|
+
Generated by \`ctxloom init --host=aider\`. Re-run to update.
|
|
11730
|
+
|
|
11731
|
+
`;
|
|
11732
|
+
var COPILOT_HEADER = `# ctxloom \u2014 GitHub Copilot instructions
|
|
11733
|
+
|
|
11734
|
+
Generated by \`ctxloom init --host=copilot\`. Re-run to update.
|
|
11735
|
+
|
|
11736
|
+
`;
|
|
11737
|
+
var WINDSURF_HEADER = `# ctxloom \u2014 Windsurf agent rules
|
|
11738
|
+
# Generated by \`ctxloom init --host=windsurf\`. Re-run to update.
|
|
11739
|
+
|
|
11740
|
+
`;
|
|
11741
|
+
var HOST_ADAPTERS = [
|
|
11742
|
+
// Note: claude / agents / gemini are NOT defined as HostAdapters in
|
|
11743
|
+
// this list — they predate the matrix and live in their own writeRulesBlock
|
|
11744
|
+
// path with HMAC-wrapped Markdown blocks. Future refactor may unify.
|
|
11745
|
+
{
|
|
11746
|
+
id: "cursor",
|
|
11747
|
+
path: ".cursorrules",
|
|
11748
|
+
defaultEnabled: false,
|
|
11749
|
+
render: () => CURSOR_HEADER + plainBody() + "\n",
|
|
11750
|
+
isCanonical(current) {
|
|
11751
|
+
return current === this.render();
|
|
11752
|
+
}
|
|
11753
|
+
},
|
|
11754
|
+
{
|
|
11755
|
+
id: "aider",
|
|
11756
|
+
path: "CONVENTIONS.md",
|
|
11757
|
+
defaultEnabled: false,
|
|
11758
|
+
render: () => AIDER_HEADER + RULES_BLOCK_CONTENT + "\n",
|
|
11759
|
+
isCanonical(current) {
|
|
11760
|
+
return current === this.render();
|
|
11761
|
+
}
|
|
11762
|
+
},
|
|
11763
|
+
{
|
|
11764
|
+
id: "copilot",
|
|
11765
|
+
path: ".github/copilot-instructions.md",
|
|
11766
|
+
defaultEnabled: false,
|
|
11767
|
+
render: () => COPILOT_HEADER + RULES_BLOCK_CONTENT + "\n",
|
|
11768
|
+
isCanonical(current) {
|
|
11769
|
+
return current === this.render();
|
|
11770
|
+
}
|
|
11771
|
+
},
|
|
11772
|
+
{
|
|
11773
|
+
id: "windsurf",
|
|
11774
|
+
path: ".windsurfrules",
|
|
11775
|
+
defaultEnabled: false,
|
|
11776
|
+
render: () => WINDSURF_HEADER + plainBody() + "\n",
|
|
11777
|
+
isCanonical(current) {
|
|
11778
|
+
return current === this.render();
|
|
11779
|
+
}
|
|
11780
|
+
}
|
|
11781
|
+
];
|
|
11782
|
+
var SUPPORTED_HOST_IDS = HOST_ADAPTERS.map((h) => h.id);
|
|
11783
|
+
|
|
11784
|
+
// ../../packages/core/src/install/hmacBlock.ts
|
|
11785
|
+
import crypto6 from "crypto";
|
|
11786
|
+
|
|
11587
11787
|
// server/loader.ts
|
|
11588
11788
|
async function loadContext(root) {
|
|
11589
|
-
const absRoot =
|
|
11789
|
+
const absRoot = path40.resolve(root);
|
|
11590
11790
|
const overlay = new GitOverlayStore(absRoot);
|
|
11591
11791
|
const gitEnabled = await overlay.loadSnapshot();
|
|
11592
11792
|
const graph = new DependencyGraph();
|
|
@@ -11839,21 +12039,21 @@ function buildOwnershipRouter(ctx) {
|
|
|
11839
12039
|
|
|
11840
12040
|
// server/routes/file.ts
|
|
11841
12041
|
import { Router as Router7 } from "express";
|
|
11842
|
-
import
|
|
11843
|
-
import
|
|
12042
|
+
import fs32 from "fs/promises";
|
|
12043
|
+
import path41 from "path";
|
|
11844
12044
|
function buildFileRouter(ctx) {
|
|
11845
12045
|
const router = Router7();
|
|
11846
12046
|
router.get("/", async (req, res) => {
|
|
11847
12047
|
const rel = req.query.path;
|
|
11848
12048
|
if (!rel) return res.status(400).json({ error: "missing path" });
|
|
11849
|
-
const abs =
|
|
11850
|
-
const rootBoundary = ctx.root.endsWith(
|
|
12049
|
+
const abs = path41.resolve(ctx.root, rel);
|
|
12050
|
+
const rootBoundary = ctx.root.endsWith(path41.sep) ? ctx.root : ctx.root + path41.sep;
|
|
11851
12051
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11852
12052
|
return res.status(403).json({ error: "forbidden" });
|
|
11853
12053
|
}
|
|
11854
12054
|
try {
|
|
11855
|
-
const content = await
|
|
11856
|
-
const ext =
|
|
12055
|
+
const content = await fs32.readFile(abs, "utf-8");
|
|
12056
|
+
const ext = path41.extname(abs).slice(1);
|
|
11857
12057
|
res.json({ content, lines: content.split("\n").length, ext });
|
|
11858
12058
|
} catch {
|
|
11859
12059
|
res.status(404).json({ error: "not found" });
|
|
@@ -11865,7 +12065,7 @@ function buildFileRouter(ctx) {
|
|
|
11865
12065
|
// server/routes/open.ts
|
|
11866
12066
|
import { Router as Router8 } from "express";
|
|
11867
12067
|
import { execFile as execFile2 } from "child_process";
|
|
11868
|
-
import
|
|
12068
|
+
import path42 from "path";
|
|
11869
12069
|
function tryOpen(bin, abs) {
|
|
11870
12070
|
return new Promise((resolve) => {
|
|
11871
12071
|
execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
|
|
@@ -11876,8 +12076,8 @@ function buildOpenRouter(ctx) {
|
|
|
11876
12076
|
router.post("/", async (req, res) => {
|
|
11877
12077
|
const rel = req.body?.path;
|
|
11878
12078
|
if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
|
|
11879
|
-
const abs =
|
|
11880
|
-
const rootBoundary = ctx.root.endsWith(
|
|
12079
|
+
const abs = path42.resolve(ctx.root, rel);
|
|
12080
|
+
const rootBoundary = ctx.root.endsWith(path42.sep) ? ctx.root : ctx.root + path42.sep;
|
|
11881
12081
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11882
12082
|
return res.status(403).json({ error: "forbidden" });
|
|
11883
12083
|
}
|
|
@@ -11889,8 +12089,8 @@ function buildOpenRouter(ctx) {
|
|
|
11889
12089
|
|
|
11890
12090
|
// server/routes/tokens.ts
|
|
11891
12091
|
import { Router as Router9 } from "express";
|
|
11892
|
-
import
|
|
11893
|
-
import
|
|
12092
|
+
import path43 from "path";
|
|
12093
|
+
import fs33 from "fs";
|
|
11894
12094
|
var CHARS_PER_TOKEN = 4;
|
|
11895
12095
|
var cache = null;
|
|
11896
12096
|
function buildTokensRouter(ctx) {
|
|
@@ -11905,9 +12105,9 @@ function buildTokensRouter(ctx) {
|
|
|
11905
12105
|
let fullChars = 0;
|
|
11906
12106
|
let skeletonChars = 0;
|
|
11907
12107
|
for (const file of files) {
|
|
11908
|
-
const absPath =
|
|
12108
|
+
const absPath = path43.join(ctx.root, file);
|
|
11909
12109
|
try {
|
|
11910
|
-
const content =
|
|
12110
|
+
const content = fs33.readFileSync(absPath, "utf-8");
|
|
11911
12111
|
fullChars += content.length;
|
|
11912
12112
|
const skeleton = await skeletonizer.skeletonize(absPath);
|
|
11913
12113
|
skeletonChars += skeleton.length;
|
|
@@ -12008,18 +12208,18 @@ function buildFileTrendsRouter(ctx) {
|
|
|
12008
12208
|
|
|
12009
12209
|
// server/routes/projects.ts
|
|
12010
12210
|
import { Router as Router12 } from "express";
|
|
12011
|
-
import
|
|
12211
|
+
import path45 from "path";
|
|
12012
12212
|
|
|
12013
12213
|
// server/projects.ts
|
|
12014
12214
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
12015
12215
|
import os8 from "os";
|
|
12016
|
-
import
|
|
12017
|
-
import
|
|
12216
|
+
import path44 from "path";
|
|
12217
|
+
import crypto7 from "crypto";
|
|
12018
12218
|
var HOME = os8.homedir();
|
|
12019
|
-
var REGISTRY_PATH =
|
|
12219
|
+
var REGISTRY_PATH = path44.join(HOME, ".ctxloom", "repos.json");
|
|
12020
12220
|
function slugFor(root) {
|
|
12021
|
-
const abs =
|
|
12022
|
-
return
|
|
12221
|
+
const abs = path44.resolve(root);
|
|
12222
|
+
return crypto7.createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
12023
12223
|
}
|
|
12024
12224
|
function readRegistry() {
|
|
12025
12225
|
if (!existsSync5(REGISTRY_PATH)) return [];
|
|
@@ -12033,27 +12233,27 @@ function readRegistry() {
|
|
|
12033
12233
|
}
|
|
12034
12234
|
}
|
|
12035
12235
|
function listProjects(defaultRoot) {
|
|
12036
|
-
const absDefault =
|
|
12236
|
+
const absDefault = path44.resolve(defaultRoot);
|
|
12037
12237
|
const out = [
|
|
12038
12238
|
{
|
|
12039
12239
|
slug: slugFor(absDefault),
|
|
12040
|
-
name:
|
|
12240
|
+
name: path44.basename(absDefault) || absDefault,
|
|
12041
12241
|
root: absDefault,
|
|
12042
12242
|
isDefault: true,
|
|
12043
|
-
hasSnapshot: existsSync5(
|
|
12243
|
+
hasSnapshot: existsSync5(path44.join(absDefault, ".ctxloom"))
|
|
12044
12244
|
}
|
|
12045
12245
|
];
|
|
12046
12246
|
const seen = /* @__PURE__ */ new Set([absDefault]);
|
|
12047
12247
|
for (const entry of readRegistry()) {
|
|
12048
|
-
const abs =
|
|
12248
|
+
const abs = path44.resolve(entry.root);
|
|
12049
12249
|
if (seen.has(abs)) continue;
|
|
12050
12250
|
seen.add(abs);
|
|
12051
12251
|
const item = {
|
|
12052
12252
|
slug: slugFor(abs),
|
|
12053
|
-
name: entry.name ?? (
|
|
12253
|
+
name: entry.name ?? (path44.basename(abs) || abs),
|
|
12054
12254
|
root: abs,
|
|
12055
12255
|
isDefault: false,
|
|
12056
|
-
hasSnapshot: existsSync5(
|
|
12256
|
+
hasSnapshot: existsSync5(path44.join(abs, ".ctxloom"))
|
|
12057
12257
|
};
|
|
12058
12258
|
if (entry.alias !== void 0) item.alias = entry.alias;
|
|
12059
12259
|
out.push(item);
|
|
@@ -12106,7 +12306,7 @@ function buildProjectsRouter(deps) {
|
|
|
12106
12306
|
} catch (err) {
|
|
12107
12307
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12108
12308
|
res.status(500).json({
|
|
12109
|
-
error: `failed to switch to ${
|
|
12309
|
+
error: `failed to switch to ${path45.basename(target.root)}: ${detail}`
|
|
12110
12310
|
});
|
|
12111
12311
|
}
|
|
12112
12312
|
});
|
|
@@ -12163,7 +12363,7 @@ function buildTelemetryRouter() {
|
|
|
12163
12363
|
}
|
|
12164
12364
|
|
|
12165
12365
|
// server/index.ts
|
|
12166
|
-
var __dirname2 =
|
|
12366
|
+
var __dirname2 = path46.dirname(fileURLToPath2(import.meta.url));
|
|
12167
12367
|
async function startDashboard(options) {
|
|
12168
12368
|
const { root, port, open } = options;
|
|
12169
12369
|
console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
|
|
@@ -12222,9 +12422,9 @@ async function startDashboard(options) {
|
|
|
12222
12422
|
}
|
|
12223
12423
|
activeWatcher = null;
|
|
12224
12424
|
}
|
|
12225
|
-
const snapshotDir =
|
|
12425
|
+
const snapshotDir = path46.join(targetRoot, ".ctxloom");
|
|
12226
12426
|
try {
|
|
12227
|
-
activeWatcher =
|
|
12427
|
+
activeWatcher = fs34.watch(snapshotDir, (_event, filename) => {
|
|
12228
12428
|
if (!filename || !filename.includes("snapshot")) return;
|
|
12229
12429
|
if (debounce) clearTimeout(debounce);
|
|
12230
12430
|
debounce = setTimeout(async () => {
|
|
@@ -12253,12 +12453,12 @@ async function startDashboard(options) {
|
|
|
12253
12453
|
attachSnapshotWatcher(newRoot);
|
|
12254
12454
|
}
|
|
12255
12455
|
}));
|
|
12256
|
-
const clientDist =
|
|
12257
|
-
const clientDistExists =
|
|
12456
|
+
const clientDist = path46.join(__dirname2, "../dashboard/client");
|
|
12457
|
+
const clientDistExists = fs34.existsSync(path46.join(clientDist, "index.html"));
|
|
12258
12458
|
if (clientDistExists) {
|
|
12259
12459
|
app.use(express.static(clientDist, { dotfiles: "allow" }));
|
|
12260
12460
|
app.get(/.*/, (_req, res) => {
|
|
12261
|
-
res.sendFile(
|
|
12461
|
+
res.sendFile(path46.join(clientDist, "index.html"), { dotfiles: "allow" });
|
|
12262
12462
|
});
|
|
12263
12463
|
} else {
|
|
12264
12464
|
app.get(/^\/(?!api\/).*/, (_req, res) => {
|