ctxr-cli 0.34.0 → 0.36.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 +50 -7
- package/dist/commands/land.d.ts +1 -1
- package/dist/commands/land.d.ts.map +1 -1
- package/dist/commands/land.js +8 -4
- package/dist/commands/land.js.map +1 -1
- package/dist/commands/plugin-build.d.ts +33 -0
- package/dist/commands/plugin-build.d.ts.map +1 -0
- package/dist/commands/plugin-build.js +51 -0
- package/dist/commands/plugin-build.js.map +1 -0
- package/dist/commands/publish-check.d.ts.map +1 -1
- package/dist/commands/publish-check.js +35 -4
- package/dist/commands/publish-check.js.map +1 -1
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +129 -23
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/session-start.d.ts.map +1 -1
- package/dist/commands/session-start.js +12 -38
- package/dist/commands/session-start.js.map +1 -1
- package/dist/commands/session-submit.d.ts.map +1 -1
- package/dist/commands/session-submit.js +2 -0
- package/dist/commands/session-submit.js.map +1 -1
- package/dist/commands/sync.d.ts +50 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +186 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/version.d.ts.map +1 -1
- package/dist/commands/version.js +6 -3
- package/dist/commands/version.js.map +1 -1
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +11 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/approval/sessions.d.ts.map +1 -1
- package/dist/core/approval/sessions.js +4 -2
- package/dist/core/approval/sessions.js.map +1 -1
- package/dist/core/browse/approve.d.ts.map +1 -1
- package/dist/core/browse/approve.js +12 -2
- package/dist/core/browse/approve.js.map +1 -1
- package/dist/core/browse/page-url.d.ts +13 -0
- package/dist/core/browse/page-url.d.ts.map +1 -1
- package/dist/core/browse/page-url.js +17 -0
- package/dist/core/browse/page-url.js.map +1 -1
- package/dist/core/browse/pull-preview.d.ts +67 -0
- package/dist/core/browse/pull-preview.d.ts.map +1 -0
- package/dist/core/browse/pull-preview.js +154 -0
- package/dist/core/browse/pull-preview.js.map +1 -0
- package/dist/core/browse/webhook.d.ts +38 -0
- package/dist/core/browse/webhook.d.ts.map +1 -0
- package/dist/core/browse/webhook.js +102 -0
- package/dist/core/browse/webhook.js.map +1 -0
- package/dist/core/errors.d.ts +16 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +27 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/fs/atomic.d.ts +3 -2
- package/dist/core/fs/atomic.d.ts.map +1 -1
- package/dist/core/fs/atomic.js +3 -2
- package/dist/core/fs/atomic.js.map +1 -1
- package/dist/core/git/exec.d.ts +16 -1
- package/dist/core/git/exec.d.ts.map +1 -1
- package/dist/core/git/exec.js +15 -4
- package/dist/core/git/exec.js.map +1 -1
- package/dist/core/git/fast-forward.d.ts +23 -0
- package/dist/core/git/fast-forward.d.ts.map +1 -1
- package/dist/core/git/fast-forward.js +24 -0
- package/dist/core/git/fast-forward.js.map +1 -1
- package/dist/core/git/hardened.d.ts +15 -0
- package/dist/core/git/hardened.d.ts.map +1 -0
- package/dist/core/git/hardened.js +123 -0
- package/dist/core/git/hardened.js.map +1 -0
- package/dist/core/install-kind.d.ts +10 -3
- package/dist/core/install-kind.d.ts.map +1 -1
- package/dist/core/install-kind.js +48 -14
- package/dist/core/install-kind.js.map +1 -1
- package/dist/core/mcp/cli-runner.d.ts +41 -0
- package/dist/core/mcp/cli-runner.d.ts.map +1 -0
- package/dist/core/mcp/cli-runner.js +81 -0
- package/dist/core/mcp/cli-runner.js.map +1 -0
- package/dist/core/mcp/jsonrpc.d.ts +13 -1
- package/dist/core/mcp/jsonrpc.d.ts.map +1 -1
- package/dist/core/mcp/jsonrpc.js +1 -1
- package/dist/core/mcp/jsonrpc.js.map +1 -1
- package/dist/core/mcp/member-sessions.d.ts +48 -0
- package/dist/core/mcp/member-sessions.d.ts.map +1 -0
- package/dist/core/mcp/member-sessions.js +85 -0
- package/dist/core/mcp/member-sessions.js.map +1 -0
- package/dist/core/mcp/ownership.d.ts +51 -0
- package/dist/core/mcp/ownership.d.ts.map +1 -0
- package/dist/core/mcp/ownership.js +101 -0
- package/dist/core/mcp/ownership.js.map +1 -0
- package/dist/core/mcp/paths.d.ts +48 -0
- package/dist/core/mcp/paths.d.ts.map +1 -0
- package/dist/core/mcp/paths.js +111 -0
- package/dist/core/mcp/paths.js.map +1 -0
- package/dist/core/mcp/sessions.d.ts +24 -46
- package/dist/core/mcp/sessions.d.ts.map +1 -1
- package/dist/core/mcp/sessions.js +31 -103
- package/dist/core/mcp/sessions.js.map +1 -1
- package/dist/core/mcp/tool-map.d.ts +94 -0
- package/dist/core/mcp/tool-map.d.ts.map +1 -0
- package/dist/core/mcp/tool-map.js +211 -0
- package/dist/core/mcp/tool-map.js.map +1 -0
- package/dist/core/mcp/tools/capture.d.ts +7 -2
- package/dist/core/mcp/tools/capture.d.ts.map +1 -1
- package/dist/core/mcp/tools/capture.js +69 -18
- package/dist/core/mcp/tools/capture.js.map +1 -1
- package/dist/core/mcp/tools/cli.d.ts +97 -0
- package/dist/core/mcp/tools/cli.d.ts.map +1 -0
- package/dist/core/mcp/tools/cli.js +271 -0
- package/dist/core/mcp/tools/cli.js.map +1 -0
- package/dist/core/mcp/tools/edit.d.ts +62 -28
- package/dist/core/mcp/tools/edit.d.ts.map +1 -1
- package/dist/core/mcp/tools/edit.js +182 -153
- package/dist/core/mcp/tools/edit.js.map +1 -1
- package/dist/core/mcp/tools/files.d.ts +37 -11
- package/dist/core/mcp/tools/files.d.ts.map +1 -1
- package/dist/core/mcp/tools/files.js +122 -100
- package/dist/core/mcp/tools/files.js.map +1 -1
- package/dist/core/mcp/tools/index.d.ts +12 -3
- package/dist/core/mcp/tools/index.d.ts.map +1 -1
- package/dist/core/mcp/tools/index.js +161 -58
- package/dist/core/mcp/tools/index.js.map +1 -1
- package/dist/core/mcp/tools/sessions.d.ts +145 -0
- package/dist/core/mcp/tools/sessions.d.ts.map +1 -0
- package/dist/core/mcp/tools/sessions.js +226 -0
- package/dist/core/mcp/tools/sessions.js.map +1 -0
- package/dist/core/npm-prefix.d.ts +10 -0
- package/dist/core/npm-prefix.d.ts.map +1 -0
- package/dist/core/npm-prefix.js +11 -0
- package/dist/core/npm-prefix.js.map +1 -0
- package/dist/core/plugin/build.d.ts +70 -0
- package/dist/core/plugin/build.d.ts.map +1 -0
- package/dist/core/plugin/build.js +159 -0
- package/dist/core/plugin/build.js.map +1 -0
- package/dist/core/plugin/routes.d.ts +35 -0
- package/dist/core/plugin/routes.d.ts.map +1 -0
- package/dist/core/plugin/routes.js +77 -0
- package/dist/core/plugin/routes.js.map +1 -0
- package/dist/core/plugin/zip.d.ts +17 -0
- package/dist/core/plugin/zip.d.ts.map +1 -0
- package/dist/core/plugin/zip.js +75 -0
- package/dist/core/plugin/zip.js.map +1 -0
- package/dist/core/session/start.d.ts +46 -0
- package/dist/core/session/start.d.ts.map +1 -0
- package/dist/core/session/start.js +33 -0
- package/dist/core/session/start.js.map +1 -0
- package/dist/core/session/submit.d.ts +7 -0
- package/dist/core/session/submit.d.ts.map +1 -1
- package/dist/core/session/submit.js +8 -0
- package/dist/core/session/submit.js.map +1 -1
- package/dist/core/skills.d.ts +20 -2
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +56 -3
- package/dist/core/skills.js.map +1 -1
- package/dist/core/store-update-runner.d.ts +57 -0
- package/dist/core/store-update-runner.d.ts.map +1 -0
- package/dist/core/{approval/lander.js → store-update-runner.js} +41 -17
- package/dist/core/store-update-runner.js.map +1 -0
- package/dist/run.d.ts +12 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +93 -58
- package/dist/run.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/approve/pages-note-pull.md +1 -0
- package/templates/conventions/baseline-conventions.md +5 -0
- package/templates/mcp/Delete.md +6 -0
- package/templates/mcp/Edit.md +6 -5
- package/templates/mcp/Glob.md +2 -2
- package/templates/mcp/Grep.md +2 -1
- package/templates/mcp/Move.md +7 -0
- package/templates/mcp/Read.md +4 -4
- package/templates/mcp/Write.md +4 -4
- package/templates/mcp/ctxr.md +7 -0
- package/templates/mcp/ctxr_write.md +8 -0
- package/templates/mcp/server-instructions.md +11 -3
- package/templates/mcp/session_discard.md +4 -0
- package/templates/mcp/session_list.md +7 -0
- package/templates/mcp/session_rebase.md +5 -0
- package/templates/mcp/session_start.md +7 -0
- package/templates/mcp/session_status.md +3 -0
- package/templates/mcp/session_submit.md +8 -0
- package/templates/plugin/README.md +13 -0
- package/templates/plugin/chatgpt-mcp.json +19 -0
- package/templates/plugin/chatgpt-plugin.json +13 -0
- package/templates/plugin/claude-mcp.json +8 -0
- package/templates/plugin/claude-plugin.json +6 -0
- package/templates/plugin/page.html +47 -0
- package/templates/skills/ctxr-publish.md +2 -1
- package/templates/skills/ctxr-upgrade.md +5 -5
- package/templates/skills/mcp/_preamble.md +44 -0
- package/dist/core/approval/lander.d.ts +0 -38
- package/dist/core/approval/lander.d.ts.map +0 -1
- package/dist/core/approval/lander.js.map +0 -1
- package/dist/core/mcp/drafts.d.ts +0 -30
- package/dist/core/mcp/drafts.d.ts.map +0 -1
- package/dist/core/mcp/drafts.js +0 -52
- package/dist/core/mcp/drafts.js.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { writeFileAtomic } from '../fs/atomic.js';
|
|
6
|
+
import { listSessionWorktreeDirs, sessionLabelFromDirName } from '../session.js';
|
|
7
|
+
import { slugifySegment } from '../slug.js';
|
|
8
|
+
/**
|
|
9
|
+
* serve-the-session-lifecycle-to-chat-apps D3 (remote-access spec, *A member
|
|
10
|
+
* works in named sessions of their own*): who started each session over MCP.
|
|
11
|
+
* One JSON file under the canonical checkout's derived cache, keyed by the
|
|
12
|
+
* worktree's directory name, written atomically and only under the surface's
|
|
13
|
+
* write mutex. A record whose worktree is gone — landed, or discarded locally —
|
|
14
|
+
* is dropped on read, and the next write persists the drop. Sessions started
|
|
15
|
+
* locally have no record and are never visible over MCP.
|
|
16
|
+
*
|
|
17
|
+
* A worktree left by the per-member draft of `2026-10-23-serve-the-store-over-mcp`
|
|
18
|
+
* carries no record; it is adopted for the member whose email its label was
|
|
19
|
+
* derived from, so its pull request stays the member's to answer.
|
|
20
|
+
*/
|
|
21
|
+
export const OWNERSHIP_VERSION = 1;
|
|
22
|
+
/** D3: the open sessions a member may hold from `session_start`; a capture's session does not count (revision 8). */
|
|
23
|
+
export const MAX_OPEN_SESSIONS = 20;
|
|
24
|
+
const DRAFT_LABEL_PREFIX = 'mcp-draft-';
|
|
25
|
+
/** The ownership record's path under the canonical checkout, beside the read view. */
|
|
26
|
+
export function ownershipFile(canonicalRoot) {
|
|
27
|
+
return path.join(canonicalRoot, '.contexture', 'cache', 'mcp', 'sessions.json');
|
|
28
|
+
}
|
|
29
|
+
/** The label the earlier per-member draft carried: `mcp-draft-<local part>-<8 hex of the email>`. */
|
|
30
|
+
export function draftLabel(email) {
|
|
31
|
+
const local = slugifySegment(email.split('@')[0] ?? 'member').slice(0, 20).replace(/-+$/, '') || 'member';
|
|
32
|
+
const digest = createHash('sha256').update(email.toLowerCase()).digest('hex').slice(0, 8);
|
|
33
|
+
return `${DRAFT_LABEL_PREFIX}${local}-${digest}`;
|
|
34
|
+
}
|
|
35
|
+
export class Ownership {
|
|
36
|
+
canonicalRoot;
|
|
37
|
+
config;
|
|
38
|
+
constructor(canonicalRoot, config) {
|
|
39
|
+
this.canonicalRoot = canonicalRoot;
|
|
40
|
+
this.config = config;
|
|
41
|
+
}
|
|
42
|
+
worktreeExists(name) {
|
|
43
|
+
return existsSync(path.join(this.canonicalRoot, this.config.session.worktrees_path, name));
|
|
44
|
+
}
|
|
45
|
+
/** Every record whose worktree still exists. */
|
|
46
|
+
async read() {
|
|
47
|
+
let parsed = null;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(await readFile(ownershipFile(this.canonicalRoot), 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
if (err.code !== 'ENOENT' && !(err instanceof SyntaxError))
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
const sessions = {};
|
|
56
|
+
for (const [name, record] of Object.entries(parsed?.sessions ?? {})) {
|
|
57
|
+
if (typeof record?.owner === 'string' && this.worktreeExists(name))
|
|
58
|
+
sessions[name] = record;
|
|
59
|
+
}
|
|
60
|
+
return sessions;
|
|
61
|
+
}
|
|
62
|
+
async persist(sessions) {
|
|
63
|
+
const file = ownershipFile(this.canonicalRoot);
|
|
64
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
65
|
+
await writeFileAtomic(file, `${JSON.stringify({ version: OWNERSHIP_VERSION, sessions }, null, 2)}\n`);
|
|
66
|
+
}
|
|
67
|
+
/** Under the write mutex. */
|
|
68
|
+
async record(name, record) {
|
|
69
|
+
const sessions = await this.read();
|
|
70
|
+
sessions[name] = { ...record, owner: record.owner.toLowerCase() };
|
|
71
|
+
await this.persist(sessions);
|
|
72
|
+
}
|
|
73
|
+
/** Under the write mutex. */
|
|
74
|
+
async forget(name) {
|
|
75
|
+
const sessions = await this.read();
|
|
76
|
+
delete sessions[name];
|
|
77
|
+
await this.persist(sessions);
|
|
78
|
+
}
|
|
79
|
+
/** The member's sessions on disk, recorded or adopted, by directory name. */
|
|
80
|
+
async ownedBy(email) {
|
|
81
|
+
const who = email.toLowerCase();
|
|
82
|
+
const sessions = await this.read();
|
|
83
|
+
const owned = Object.entries(sessions)
|
|
84
|
+
.filter(([, record]) => record.owner === who)
|
|
85
|
+
.map(([name, record]) => ({ name, record }));
|
|
86
|
+
const draft = draftLabel(who);
|
|
87
|
+
for (const name of await listSessionWorktreeDirs(this.canonicalRoot, this.config)) {
|
|
88
|
+
if (sessions[name] === undefined && sessionLabelFromDirName(this.config, name) === draft)
|
|
89
|
+
owned.push({ name, record: { owner: who, kind: 'session', started_at: null } });
|
|
90
|
+
}
|
|
91
|
+
return owned.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
92
|
+
}
|
|
93
|
+
/** Every capture session on disk, whoever started it. */
|
|
94
|
+
async captures() {
|
|
95
|
+
return Object.entries(await this.read())
|
|
96
|
+
.filter(([, record]) => record.kind === 'capture')
|
|
97
|
+
.map(([name, record]) => ({ name, record }))
|
|
98
|
+
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownership.js","sourceRoot":"","sources":["../../../src/core/mcp/ownership.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { StoreConfig } from '../../config/schema.js';
|
|
2
|
+
import { ToolRefusal } from './jsonrpc.js';
|
|
3
|
+
/**
|
|
4
|
+
* serve-the-session-lifecycle-to-chat-apps D5: what the file tools and the
|
|
5
|
+
* command runners may read and change, as predicates over a store-relative,
|
|
6
|
+
* `/`-separated path. Kept free of any session or snapshot, so the file tools
|
|
7
|
+
* over the served commit, the same tools inside a session, and `ctxr_write`'s
|
|
8
|
+
* before- and after-run checks all ask the same question.
|
|
9
|
+
*/
|
|
10
|
+
/** The store's entry document, readable though it is not a note. */
|
|
11
|
+
export declare const ENTRY_DOCUMENT = "AGENTS.md";
|
|
12
|
+
export type ProtectedReason = 'configuration' | 'git' | 'derived' | 'worktrees' | 'skills';
|
|
13
|
+
/**
|
|
14
|
+
* D5, revision 7: the one protected-path predicate. Inside a session a member
|
|
15
|
+
* may change what a local session may change except these: the configuration
|
|
16
|
+
* (it grants MCP scopes), anything whose first segment begins with `.git` —
|
|
17
|
+
* the hooks directory among them, because a commit in a session worktree runs
|
|
18
|
+
* that worktree's hooks on the server — derived and cache paths, the
|
|
19
|
+
* worktrees path, and the managed skills path, which `ctxr update` owns.
|
|
20
|
+
* Null when the path may be changed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function protectedReason(rel: string, config: StoreConfig): ProtectedReason | null;
|
|
23
|
+
export declare function protectedRefusal(rel: string, reason: ProtectedReason): ToolRefusal;
|
|
24
|
+
/** D5: the paths the owned skills read that are not notes: templates, house conventions, the mission, the log and the skills. */
|
|
25
|
+
export declare function widenedReadablePrefixes(config: StoreConfig): string[];
|
|
26
|
+
/** Never read or returned, whatever else applies: configuration, `.git`, derived paths and the worktrees path. */
|
|
27
|
+
export declare function neverReadablePrefixes(config: StoreConfig): string[];
|
|
28
|
+
/**
|
|
29
|
+
* D5: hidden from the file tools. `retrieval.exclude_paths` stays hidden — it
|
|
30
|
+
* is the operator's declared intent — except the capture tier, which `Read`
|
|
31
|
+
* marks, and the widened readable set, which the skills need.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hiddenPrefixes(config: StoreConfig): string[];
|
|
34
|
+
/** Whether `rel` is a path the file tools may name at all (a file or a directory to search). */
|
|
35
|
+
export declare function nameablePath(rel: string, config: StoreConfig): boolean;
|
|
36
|
+
/** What `Read` answers for, as one predicate: a note, a markdown capture, the entry document, or the widened set. Grep and Glob return nothing else. */
|
|
37
|
+
export declare function readablePath(rel: string, config: StoreConfig): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The exclusions ripgrep is given: the hidden prefixes that contain nothing in
|
|
40
|
+
* the widened set. A hidden prefix that does contain some (the default
|
|
41
|
+
* `.contexture/` holds the templates) is searched and filtered by
|
|
42
|
+
* `readablePath` afterwards, because a later positive glob would turn
|
|
43
|
+
* ripgrep's exclusions into an allowlist.
|
|
44
|
+
*/
|
|
45
|
+
export declare function searchExclusions(config: StoreConfig): string[];
|
|
46
|
+
/** A caller's path as a normalized store-relative one (a leading `/` names the store root); refused when it leaves the store. `''` is the root. */
|
|
47
|
+
export declare function normalizeStorePath(input: string): string;
|
|
48
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/paths.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;GAMG;AAEH,oEAAoE;AACpE,eAAO,MAAM,cAAc,cAAc,CAAC;AAE1C,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,KAAK,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE3F;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,CAOxF;AAUD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,WAAW,CAElF;AAED,iIAAiI;AACjI,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAQrE;AAED,kHAAkH;AAClH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAG5D;AAED,gGAAgG;AAChG,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAKtE;AAED,wJAAwJ;AACxJ,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAKtE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAG9D;AAED,mJAAmJ;AACnJ,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { DEFAULT_LOG_PATH } from '../../config/defaults.js';
|
|
3
|
+
import { isUnderAnyPrefix, isUnderPrefix } from '../fs/prefix.js';
|
|
4
|
+
import { admitsNotePath } from '../notes/list.js';
|
|
5
|
+
import { CONFIG_FILE_NAME } from '../root.js';
|
|
6
|
+
import { ToolRefusal } from './jsonrpc.js';
|
|
7
|
+
/**
|
|
8
|
+
* serve-the-session-lifecycle-to-chat-apps D5: what the file tools and the
|
|
9
|
+
* command runners may read and change, as predicates over a store-relative,
|
|
10
|
+
* `/`-separated path. Kept free of any session or snapshot, so the file tools
|
|
11
|
+
* over the served commit, the same tools inside a session, and `ctxr_write`'s
|
|
12
|
+
* before- and after-run checks all ask the same question.
|
|
13
|
+
*/
|
|
14
|
+
/** The store's entry document, readable though it is not a note. */
|
|
15
|
+
export const ENTRY_DOCUMENT = 'AGENTS.md';
|
|
16
|
+
/**
|
|
17
|
+
* D5, revision 7: the one protected-path predicate. Inside a session a member
|
|
18
|
+
* may change what a local session may change except these: the configuration
|
|
19
|
+
* (it grants MCP scopes), anything whose first segment begins with `.git` —
|
|
20
|
+
* the hooks directory among them, because a commit in a session worktree runs
|
|
21
|
+
* that worktree's hooks on the server — derived and cache paths, the
|
|
22
|
+
* worktrees path, and the managed skills path, which `ctxr update` owns.
|
|
23
|
+
* Null when the path may be changed.
|
|
24
|
+
*/
|
|
25
|
+
export function protectedReason(rel, config) {
|
|
26
|
+
if (rel === CONFIG_FILE_NAME)
|
|
27
|
+
return 'configuration';
|
|
28
|
+
if ((rel.split('/')[0] ?? '').startsWith('.git'))
|
|
29
|
+
return 'git';
|
|
30
|
+
if (isUnderAnyPrefix(rel, config.derived.paths))
|
|
31
|
+
return 'derived';
|
|
32
|
+
if (isUnderPrefix(rel, config.session.worktrees_path))
|
|
33
|
+
return 'worktrees';
|
|
34
|
+
if (isUnderPrefix(rel, config.harness.skills_path))
|
|
35
|
+
return 'skills';
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const PROTECTED_WORDS = {
|
|
39
|
+
configuration: 'the store configuration',
|
|
40
|
+
git: "git's own files and the hooks directory, which a commit on the server would run",
|
|
41
|
+
derived: 'a derived or cache path',
|
|
42
|
+
worktrees: 'the session worktrees path',
|
|
43
|
+
skills: 'the managed skills path',
|
|
44
|
+
};
|
|
45
|
+
export function protectedRefusal(rel, reason) {
|
|
46
|
+
return new ToolRefusal('mcp.path_protected', `"${rel}" is ${PROTECTED_WORDS[reason]}, which cannot be changed from here; change it in a local session.`, { path: rel, reason });
|
|
47
|
+
}
|
|
48
|
+
/** D5: the paths the owned skills read that are not notes: templates, house conventions, the mission, the log and the skills. */
|
|
49
|
+
export function widenedReadablePrefixes(config) {
|
|
50
|
+
return [
|
|
51
|
+
config.templates.path,
|
|
52
|
+
config.harness.guidance_path,
|
|
53
|
+
config.harness.skills_path,
|
|
54
|
+
config.organize.log_path ?? DEFAULT_LOG_PATH,
|
|
55
|
+
...(config.organize.mission_path !== undefined ? [config.organize.mission_path] : []),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/** Never read or returned, whatever else applies: configuration, `.git`, derived paths and the worktrees path. */
|
|
59
|
+
export function neverReadablePrefixes(config) {
|
|
60
|
+
return [CONFIG_FILE_NAME, '.git', ...config.derived.paths, config.session.worktrees_path];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* D5: hidden from the file tools. `retrieval.exclude_paths` stays hidden — it
|
|
64
|
+
* is the operator's declared intent — except the capture tier, which `Read`
|
|
65
|
+
* marks, and the widened readable set, which the skills need.
|
|
66
|
+
*/
|
|
67
|
+
export function hiddenPrefixes(config) {
|
|
68
|
+
const captureRoot = config.ingest.capture_root.replace(/\/+$/, '');
|
|
69
|
+
return [...config.retrieval.exclude_paths.filter((p) => p.replace(/\/+$/, '') !== captureRoot), ...neverReadablePrefixes(config)];
|
|
70
|
+
}
|
|
71
|
+
/** Whether `rel` is a path the file tools may name at all (a file or a directory to search). */
|
|
72
|
+
export function nameablePath(rel, config) {
|
|
73
|
+
if (rel === '')
|
|
74
|
+
return true;
|
|
75
|
+
if (isUnderAnyPrefix(rel, neverReadablePrefixes(config)))
|
|
76
|
+
return false;
|
|
77
|
+
if (isUnderAnyPrefix(rel, widenedReadablePrefixes(config)))
|
|
78
|
+
return true;
|
|
79
|
+
return !isUnderAnyPrefix(rel, hiddenPrefixes(config));
|
|
80
|
+
}
|
|
81
|
+
/** What `Read` answers for, as one predicate: a note, a markdown capture, the entry document, or the widened set. Grep and Glob return nothing else. */
|
|
82
|
+
export function readablePath(rel, config) {
|
|
83
|
+
if (!nameablePath(rel, config) || rel === '')
|
|
84
|
+
return false;
|
|
85
|
+
if (isUnderAnyPrefix(rel, widenedReadablePrefixes(config)))
|
|
86
|
+
return true;
|
|
87
|
+
if (admitsNotePath(rel, config) || rel === ENTRY_DOCUMENT)
|
|
88
|
+
return true;
|
|
89
|
+
return rel.endsWith('.md') && isUnderAnyPrefix(rel, [config.ingest.capture_root]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The exclusions ripgrep is given: the hidden prefixes that contain nothing in
|
|
93
|
+
* the widened set. A hidden prefix that does contain some (the default
|
|
94
|
+
* `.contexture/` holds the templates) is searched and filtered by
|
|
95
|
+
* `readablePath` afterwards, because a later positive glob would turn
|
|
96
|
+
* ripgrep's exclusions into an allowlist.
|
|
97
|
+
*/
|
|
98
|
+
export function searchExclusions(config) {
|
|
99
|
+
const widened = widenedReadablePrefixes(config).map((p) => p.replace(/\/+$/, ''));
|
|
100
|
+
return hiddenPrefixes(config).filter((hidden) => !widened.some((w) => isUnderPrefix(w, hidden)) || isUnderAnyPrefix(hidden, neverReadablePrefixes(config)));
|
|
101
|
+
}
|
|
102
|
+
/** A caller's path as a normalized store-relative one (a leading `/` names the store root); refused when it leaves the store. `''` is the root. */
|
|
103
|
+
export function normalizeStorePath(input) {
|
|
104
|
+
const cleaned = input.trim().replace(/\\/g, '/');
|
|
105
|
+
const normalized = path.posix.normalize(`/${cleaned}`).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
106
|
+
if (normalized === '..' || normalized.startsWith('../') || cleaned.split('/').includes('..')) {
|
|
107
|
+
throw new ToolRefusal('mcp.path_outside_store', `"${input}" is outside the store.`);
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/mcp/paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type { StoreConfig } from '../../config/schema.js';
|
|
2
2
|
import { type ForgeCredentials } from '../forge/credentials.js';
|
|
3
|
-
import type { ForgeClient
|
|
3
|
+
import type { ForgeClient } from '../forge/github.js';
|
|
4
4
|
import type { GitRunner } from '../git/exec.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { CommitOptions } from '../session/commit.js';
|
|
6
|
+
import { type StartedSession } from '../session/start.js';
|
|
6
7
|
/**
|
|
7
8
|
* serve-the-store-over-mcp design.md D9, D13: the write side the MCP surface
|
|
8
|
-
* shares between
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* shares between its tools. Everything it writes is an ordinary session — a
|
|
10
|
+
* worktree under the configured worktrees path, on a branch under the session
|
|
11
|
+
* prefix, with a pull request — so the approval page lists it and `ctxr land`
|
|
12
|
+
* lands it with no MCP-specific path anywhere.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* serve-the-session-lifecycle-to-chat-apps D3: starting, submitting,
|
|
15
|
+
* rebasing and discarding are `src/core/session/`'s, the same calls
|
|
16
|
+
* `ctxr session <verb>` makes. What stays here is what the MCP layer adds:
|
|
17
|
+
* the server's forge credential on every remote step, the member as commit
|
|
18
|
+
* author, and the surface's wording of a refusal. The per-member draft's
|
|
19
|
+
* helpers are gone with the draft.
|
|
17
20
|
*/
|
|
18
21
|
export interface WriteDeps {
|
|
19
22
|
git: GitRunner;
|
|
@@ -31,51 +34,26 @@ export interface WriteDeps {
|
|
|
31
34
|
/** The store root's path inside its repository, when the caller has already resolved it; asked of git otherwise. */
|
|
32
35
|
repoPrefix?: string;
|
|
33
36
|
}
|
|
34
|
-
export interface Session {
|
|
35
|
-
/** The worktree directory's name: the key the approval page addresses it by. */
|
|
36
|
-
name: string;
|
|
37
|
-
path: string;
|
|
38
|
-
branch: string;
|
|
39
|
-
label: string | null;
|
|
40
|
-
}
|
|
41
|
-
export type { PullState };
|
|
42
37
|
export declare class WriteRefusal extends Error {
|
|
43
38
|
readonly code: string;
|
|
44
39
|
readonly details?: Record<string, unknown> | undefined;
|
|
45
40
|
constructor(code: string, message: string, details?: Record<string, unknown> | undefined);
|
|
46
41
|
}
|
|
47
|
-
/** One write at a time across
|
|
42
|
+
/** One write at a time across every tool that changes a worktree, the ownership record or the forge (design.md D9, D13). */
|
|
48
43
|
export declare class Mutex {
|
|
49
44
|
private tail;
|
|
50
45
|
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
export declare function sessionsLabelled(deps: WriteDeps, labelPrefix: string): Promise<Session[]>;
|
|
55
|
-
/** What the forge says about the pull requests for `branch`: the open one, else merged, else closed, else none. */
|
|
56
|
-
export declare function pullStateFor(deps: WriteDeps, branch: string): Promise<PullState>;
|
|
57
|
-
/** A session whose pull request was closed without merging is removed: worktree and local branch (D9, D13). */
|
|
58
|
-
export declare function removeSession(deps: WriteDeps, session: Session): Promise<void>;
|
|
59
|
-
/** A new session from the freshly fetched default branch, exactly as `ctxr session start` names and places one. */
|
|
60
|
-
export declare function startSession(deps: WriteDeps, label: string): Promise<Session>;
|
|
61
|
-
/** The store-relative path's location inside the repository, for `git show <branch>:<path>`. */
|
|
62
|
-
export declare function repoPathOf(deps: WriteDeps, storeRelative: string): Promise<string>;
|
|
63
|
-
/**
|
|
64
|
-
* Stages `paths` and commits them authored by the member, with the store's
|
|
65
|
-
* hooks running as for any commit. On a refusal the worktree is restored to
|
|
66
|
-
* its tip — a draft never keeps a change its pull request does not carry —
|
|
67
|
-
* and the hook's output is the refusal (design.md D13).
|
|
68
|
-
*/
|
|
69
|
-
export declare function commitAs(deps: WriteDeps, session: Session, email: string, paths: readonly string[], subject: string, trailer: string): Promise<string>;
|
|
70
|
-
export declare function restore(deps: WriteDeps, session: Session): Promise<void>;
|
|
47
|
+
/** The approval page for a session, at the server's own address. */
|
|
48
|
+
export declare function approvalUrl(deps: WriteDeps, sessionName: string): string;
|
|
71
49
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* request is found, never rewritten: its body is the draft's own.
|
|
50
|
+
* A new session from the freshly fetched default branch, through the core
|
|
51
|
+
* `ctxr session start` calls. A closed pull request's branch stays on the
|
|
52
|
+
* remote, so a generated name the remote already carries is stepped past.
|
|
76
53
|
*/
|
|
77
|
-
export declare function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
54
|
+
export declare function startSession(deps: WriteDeps, label: string | undefined): Promise<StartedSession>;
|
|
55
|
+
/** D3: the member as the commit's author, with a trailer naming them; the recording login commits when the host has no identity. */
|
|
56
|
+
export declare function memberCommit(deps: WriteDeps, email: string, trailer: string): Omit<CommitOptions, 'message'>;
|
|
57
|
+
/** A WriteRefusal or a core refusal, as a refusal the client model can read rather than a protocol error. */
|
|
58
|
+
export declare function asToolRefusal(err: unknown): unknown;
|
|
81
59
|
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/sessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG5F;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,SAAS,CAAC;IACf,8GAA8G;IAC9G,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAClD,GAAG,IAAI,IAAI,CAAC;IACZ,+GAA+G;IAC/G,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,oHAAoH;IACpH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAa,SAAQ,KAAK;IAEnC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAErB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAH5C,YACW,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACN,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA,EAI3C;CACF;AAED,4HAA4H;AAC5H,qBAAa,KAAK;IAChB,OAAO,CAAC,IAAI,CAAuC;IAEnD,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAIvC;CACF;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAExE;AASD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAYtG;AAED,oIAAoI;AACpI,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAE5G;AAED,6GAA6G;AAC7G,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAInD"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ContextureError } from '../errors.js';
|
|
2
2
|
import { runRemoteGit } from '../forge/credentials.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { firstLines, publishSession, pullStateFor as forgePullState, removeSession as removeSessionWorktree, SessionRemoteError } from '../session/remote.js';
|
|
3
|
+
import { fetchDefaultBranch } from '../session/status.js';
|
|
4
|
+
import { startSession as startCoreSession } from '../session/start.js';
|
|
5
|
+
import { ToolRefusal } from './jsonrpc.js';
|
|
7
6
|
export class WriteRefusal extends Error {
|
|
8
7
|
code;
|
|
9
8
|
details;
|
|
@@ -14,7 +13,7 @@ export class WriteRefusal extends Error {
|
|
|
14
13
|
this.name = 'WriteRefusal';
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
|
-
/** One write at a time across
|
|
16
|
+
/** One write at a time across every tool that changes a worktree, the ownership record or the forge (design.md D9, D13). */
|
|
18
17
|
export class Mutex {
|
|
19
18
|
tail = Promise.resolve();
|
|
20
19
|
run(fn) {
|
|
@@ -23,52 +22,9 @@ export class Mutex {
|
|
|
23
22
|
return next;
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
/** The session worktrees on disk whose label starts with `labelPrefix`, with their branches. */
|
|
30
|
-
export async function sessionsLabelled(deps, labelPrefix) {
|
|
31
|
-
const root = path.join(deps.canonicalRoot, deps.config.session.worktrees_path);
|
|
32
|
-
const sessions = [];
|
|
33
|
-
for (const name of await listSessionWorktreeDirs(deps.canonicalRoot, deps.config)) {
|
|
34
|
-
const label = sessionLabelFromDirName(deps.config, name);
|
|
35
|
-
if (label === null || !label.startsWith(labelPrefix))
|
|
36
|
-
continue;
|
|
37
|
-
const dir = path.join(root, name);
|
|
38
|
-
const branch = (await deps.git.run(['symbolic-ref', '--short', 'HEAD'], { cwd: dir, allowFailure: true })).stdout.trim();
|
|
39
|
-
if (branch === '')
|
|
40
|
-
continue;
|
|
41
|
-
sessions.push({ name, path: dir, branch, label });
|
|
42
|
-
}
|
|
43
|
-
return sessions.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
44
|
-
}
|
|
45
|
-
/** What the forge says about the pull requests for `branch`: the open one, else merged, else closed, else none. */
|
|
46
|
-
export async function pullStateFor(deps, branch) {
|
|
47
|
-
return forgePullState(deps.forge, branch);
|
|
48
|
-
}
|
|
49
|
-
/** A session whose pull request was closed without merging is removed: worktree and local branch (D9, D13). */
|
|
50
|
-
export async function removeSession(deps, session) {
|
|
51
|
-
await removeSessionWorktree(deps.git, deps.canonicalRoot, session);
|
|
52
|
-
}
|
|
53
|
-
/** A new session from the freshly fetched default branch, exactly as `ctxr session start` names and places one. */
|
|
54
|
-
export async function startSession(deps, label) {
|
|
55
|
-
const slug = sessionLabelSlug(label);
|
|
56
|
-
const defaultBranch = deps.config.git.default_branch;
|
|
57
|
-
await runRemoteGit(deps.git, deps.credentials, deps.env, ['fetch', '--quiet', 'origin', `+refs/heads/${defaultBranch}:refs/remotes/origin/${defaultBranch}`], {
|
|
58
|
-
cwd: deps.canonicalRoot,
|
|
59
|
-
allowFailure: true,
|
|
60
|
-
});
|
|
61
|
-
const hasRemote = (await deps.git.run(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${defaultBranch}`], { cwd: deps.canonicalRoot, allowFailure: true })).exitCode === 0;
|
|
62
|
-
// Names carry a timestamp to the second, and a closed pull request's branch stays on the remote: step past any taken name.
|
|
63
|
-
let branch = generateSessionBranchName(deps.config, deps.now(), slug);
|
|
64
|
-
for (let step = 1; step <= 60 && (await branchTaken(deps, branch)); step += 1) {
|
|
65
|
-
branch = generateSessionBranchName(deps.config, new Date(deps.now().getTime() + step * 1000), slug);
|
|
66
|
-
}
|
|
67
|
-
const dir = worktreePathFor({ root: deps.canonicalRoot, config: deps.config }, branch);
|
|
68
|
-
const added = await addWorktree(deps.git, deps.canonicalRoot, dir, branch, hasRemote ? `origin/${defaultBranch}` : defaultBranch, { allowFailure: true });
|
|
69
|
-
if (added.exitCode !== 0)
|
|
70
|
-
throw new WriteRefusal('mcp.session_not_started', `A session could not be started: ${firstLines(added.stderr)}`);
|
|
71
|
-
return { name: path.basename(dir), path: dir, branch, label: slug };
|
|
25
|
+
/** The approval page for a session, at the server's own address. */
|
|
26
|
+
export function approvalUrl(deps, sessionName) {
|
|
27
|
+
return `${deps.baseUrl.replace(/\/+$/, '')}/approve/${encodeURIComponent(sessionName)}`;
|
|
72
28
|
}
|
|
73
29
|
async function branchTaken(deps, branch) {
|
|
74
30
|
const local = await deps.git.run(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: deps.canonicalRoot, allowFailure: true });
|
|
@@ -77,58 +33,30 @@ async function branchTaken(deps, branch) {
|
|
|
77
33
|
const remote = await runRemoteGit(deps.git, deps.credentials, deps.env, ['ls-remote', '--exit-code', 'origin', `refs/heads/${branch}`], { cwd: deps.canonicalRoot, allowFailure: true });
|
|
78
34
|
return remote.exitCode === 0;
|
|
79
35
|
}
|
|
80
|
-
/** The store-relative path's location inside the repository, for `git show <branch>:<path>`. */
|
|
81
|
-
export async function repoPathOf(deps, storeRelative) {
|
|
82
|
-
const prefix = deps.repoPrefix ?? (await deps.git.run(['rev-parse', '--show-prefix'], { cwd: deps.canonicalRoot, allowFailure: true })).stdout.trim();
|
|
83
|
-
return `${prefix}${storeRelative}`;
|
|
84
|
-
}
|
|
85
36
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* and the hook's output is the refusal (design.md D13).
|
|
37
|
+
* A new session from the freshly fetched default branch, through the core
|
|
38
|
+
* `ctxr session start` calls. A closed pull request's branch stays on the
|
|
39
|
+
* remote, so a generated name the remote already carries is stepped past.
|
|
90
40
|
*/
|
|
91
|
-
export async function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
export async function restore(deps, session) {
|
|
112
|
-
await deps.git.run(['reset', '--quiet', '--hard', 'HEAD'], { cwd: session.path, allowFailure: true });
|
|
113
|
-
await deps.git.run(['clean', '-fdq'], { cwd: session.path, allowFailure: true });
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Publishes whatever the session holds that the forge does not, through the
|
|
117
|
-
* shared core's idempotent publish, so a retry after any partial failure
|
|
118
|
-
* converges on exactly one pull request (design.md D9, D13). An open pull
|
|
119
|
-
* request is found, never rewritten: its body is the draft's own.
|
|
120
|
-
*/
|
|
121
|
-
export async function publish(deps, session, pull) {
|
|
122
|
-
try {
|
|
123
|
-
return (await publishSession(deps, session, pull)).pull;
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
if (!(err instanceof SessionRemoteError))
|
|
127
|
-
throw err;
|
|
128
|
-
if (err.step === 'push') {
|
|
129
|
-
throw new WriteRefusal('mcp.push_failed', `The change is saved in its session but could not be pushed yet (${err.detail}). Retry the same call to publish it.`, { session: session.name });
|
|
130
|
-
}
|
|
131
|
-
throw new WriteRefusal('mcp.pull_request_failed', `The change is pushed but its pull request could not be opened yet (${err.detail}). Retry the same call to open it.`, { session: session.name });
|
|
132
|
-
}
|
|
41
|
+
export async function startSession(deps, label) {
|
|
42
|
+
return startCoreSession({
|
|
43
|
+
git: deps.git,
|
|
44
|
+
config: deps.config,
|
|
45
|
+
fetch: (cwd) => fetchDefaultBranch({ git: deps.git, config: deps.config, env: deps.env, credentials: deps.credentials }, cwd),
|
|
46
|
+
now: deps.now,
|
|
47
|
+
branchTaken: (branch) => branchTaken(deps, branch),
|
|
48
|
+
}, deps.canonicalRoot, label);
|
|
49
|
+
}
|
|
50
|
+
/** D3: the member as the commit's author, with a trailer naming them; the recording login commits when the host has no identity. */
|
|
51
|
+
export function memberCommit(deps, email, trailer) {
|
|
52
|
+
return { author: { name: email.split('@')[0] ?? email, email }, trailer: { key: trailer, value: email }, fallbackCommitter: deps.recordingLogin };
|
|
53
|
+
}
|
|
54
|
+
/** A WriteRefusal or a core refusal, as a refusal the client model can read rather than a protocol error. */
|
|
55
|
+
export function asToolRefusal(err) {
|
|
56
|
+
if (err instanceof WriteRefusal)
|
|
57
|
+
return new ToolRefusal(err.code, err.message, err.details);
|
|
58
|
+
if (err instanceof ContextureError)
|
|
59
|
+
return new ToolRefusal(err.finding.code, err.finding.message, err.finding.details);
|
|
60
|
+
return err;
|
|
133
61
|
}
|
|
134
62
|
//# sourceMappingURL=sessions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/core/mcp/sessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/core/mcp/sessions.ts"],"names":[],"mappings":"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"}
|