@f5-sales-demo/xcsh 19.85.7 → 19.86.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "19.85.7",
4
+ "version": "19.86.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -56,13 +56,13 @@
56
56
  "dependencies": {
57
57
  "@agentclientprotocol/sdk": "0.16.1",
58
58
  "@mozilla/readability": "^0.6",
59
- "@f5-sales-demo/xcsh-stats": "19.85.7",
60
- "@f5-sales-demo/pi-agent-core": "19.85.7",
61
- "@f5-sales-demo/pi-ai": "19.85.7",
62
- "@f5-sales-demo/pi-natives": "19.85.7",
63
- "@f5-sales-demo/pi-resource-management": "19.85.7",
64
- "@f5-sales-demo/pi-tui": "19.85.7",
65
- "@f5-sales-demo/pi-utils": "19.85.7",
59
+ "@f5-sales-demo/xcsh-stats": "19.86.0",
60
+ "@f5-sales-demo/pi-agent-core": "19.86.0",
61
+ "@f5-sales-demo/pi-ai": "19.86.0",
62
+ "@f5-sales-demo/pi-natives": "19.86.0",
63
+ "@f5-sales-demo/pi-resource-management": "19.86.0",
64
+ "@f5-sales-demo/pi-tui": "19.86.0",
65
+ "@f5-sales-demo/pi-utils": "19.86.0",
66
66
  "@sinclair/typebox": "^0.34",
67
67
  "@xterm/headless": "^6.0",
68
68
  "ajv": "^8.20",
@@ -217,8 +217,9 @@ export function renderAboutDoc(info: RuntimeBuildInfo, context: ContextStatus |
217
217
  "## What to do when asked about xcsh itself",
218
218
  "",
219
219
  "1. The version above is authoritative — it is embedded at build time in this session's BUILD_INFO and also shown in the `<workstation>` header of the system prompt. Do not run `xcsh --version` to check — that reports the installed binary, which may differ from the running session after an upgrade.",
220
- "2. Check recent changes with `gh pr list --repo f5-sales-demo/xcsh --base main --state merged --limit 20`",
221
- " or `git log --oneline -n 20` if you have a local clone. A fix may already be on `main`.",
220
+ '2. For recent changes / "what\'s new", read `xcsh://changes` it lists merged PRs live and flags',
221
+ " what shipped after your build (it falls back to `gh pr list` / `git log` when gh is unavailable).",
222
+ ' A fix may already be on `main`. For "where is X implemented?", read `xcsh://source`.',
222
223
  "3. If behavior contradicts `xcsh://…` docs, read the actual source under the repo above to determine",
223
224
  " whether the binary is wrong or the doc is stale.",
224
225
  "4. Classify the report as one of: **bug**, **feature**, **docs-drift**, or **config/usage**.",
@@ -241,6 +242,13 @@ export function renderAboutDoc(info: RuntimeBuildInfo, context: ContextStatus |
241
242
  "",
242
243
  "The improvement workflow is always: open an issue on the repo, then a PR. The user receives changes only after a new release is built and they upgrade. Do not claim a change is live until the commit above reflects it.",
243
244
  "",
245
+ "You are a GitHub / devops / security **operator**: your job is to author the issue, the",
246
+ "reproduction, the Terraform/manifests/scripts/docs, and the PR description — but **implementing**",
247
+ "feature code on xcsh, `f5-sales-demo/marketplace`, or `f5-sales-demo/api-specs-enriched` is",
248
+ "delegated to a dedicated coding harness (Claude Code / Codex) with its own dev environment.",
249
+ "To file well: clone the relevant repo, reproduce the behavior first, and follow `CONTRIBUTING.md` —",
250
+ "TDD, evidence required, no unverified claims.",
251
+ "",
244
252
  "## What NOT to assume",
245
253
  "",
246
254
  "- Do not guess the repo URL, version, or commit — use the values above.",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.85.7",
21
- "commit": "9ca40d6aab40f53a85bba28e4ff8b0f6ceed1782",
22
- "shortCommit": "9ca40d6",
20
+ "version": "19.86.0",
21
+ "commit": "24a43f94c2aca0385cb3284227d0a503de320b32",
22
+ "shortCommit": "24a43f9",
23
23
  "branch": "main",
24
- "tag": "v19.85.7",
25
- "commitDate": "2026-07-24T13:17:17Z",
26
- "buildDate": "2026-07-24T13:42:27.292Z",
24
+ "tag": "v19.86.0",
25
+ "commitDate": "2026-07-24T16:55:07Z",
26
+ "buildDate": "2026-07-24T17:20:18.596Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/9ca40d6aab40f53a85bba28e4ff8b0f6ceed1782",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.85.7"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/24a43f94c2aca0385cb3284227d0a503de320b32",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.86.0"
33
33
  };
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Resolver for xcsh://changes — "what changed recently in xcsh".
3
+ *
4
+ * xcsh's own recent history is NOT static prompt knowledge: it lives in merged
5
+ * pull requests on the source repo. This resolver fetches them live via `gh`
6
+ * and flags which ones landed after the running build, so the agent can answer
7
+ * "what's new / can you do X now?" from ground truth rather than memory.
8
+ *
9
+ * URL forms (host = "changes"):
10
+ * - xcsh://changes -> recent merged PRs on main (default limit 20)
11
+ * - xcsh://changes?limit=N -> recent merged PRs, capped at N
12
+ *
13
+ * Dynamic, uncached (mirrors xcsh://about). All shelling is injected so the
14
+ * resolver is unit-testable without a network or a `gh` binary.
15
+ */
16
+ import { $ } from "bun";
17
+ import { formatRelativeTime, type RuntimeBuildInfo } from "./build-info-runtime";
18
+ import type { InternalResource, InternalUrl } from "./types";
19
+
20
+ export interface GhResult {
21
+ readonly ok: boolean;
22
+ readonly stdout: string;
23
+ readonly stderr: string;
24
+ }
25
+
26
+ export interface MergedPr {
27
+ readonly number: number;
28
+ readonly title: string;
29
+ readonly mergedAt: string;
30
+ readonly url: string;
31
+ }
32
+
33
+ export interface ChangesDeps {
34
+ readonly resolveBuildInfo: () => Promise<RuntimeBuildInfo>;
35
+ readonly runGh: (args: string[]) => Promise<GhResult>;
36
+ readonly now: () => Date;
37
+ }
38
+
39
+ export const DEFAULT_CHANGES_LIMIT = 20;
40
+ const MAX_CHANGES_LIMIT = 100;
41
+
42
+ /** Parse the JSON array emitted by `gh pr list --json ...`. Throws on malformed input. */
43
+ export function parseMergedPrs(json: string): MergedPr[] {
44
+ const parsed = JSON.parse(json);
45
+ if (!Array.isArray(parsed)) {
46
+ throw new Error("Expected a JSON array of pull requests");
47
+ }
48
+ return parsed.map(entry => ({
49
+ number: Number(entry.number),
50
+ title: String(entry.title ?? ""),
51
+ mergedAt: String(entry.mergedAt ?? ""),
52
+ url: String(entry.url ?? ""),
53
+ }));
54
+ }
55
+
56
+ /** Derive "owner/repo" from build info, falling back to the repo URL. */
57
+ function repoSlugOf(info: RuntimeBuildInfo): string {
58
+ if (info.repoSlug) return info.repoSlug;
59
+ return info.repoUrl.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
60
+ }
61
+
62
+ function parseLimit(url: InternalUrl): number {
63
+ const raw = url.searchParams.get("limit");
64
+ if (!raw) return DEFAULT_CHANGES_LIMIT;
65
+ const n = Number.parseInt(raw, 10);
66
+ if (!Number.isFinite(n) || n <= 0) return DEFAULT_CHANGES_LIMIT;
67
+ return Math.min(n, MAX_CHANGES_LIMIT);
68
+ }
69
+
70
+ function ghArgs(slug: string, limit: number): string[] {
71
+ return [
72
+ "pr",
73
+ "list",
74
+ "--repo",
75
+ slug,
76
+ "--base",
77
+ "main",
78
+ "--state",
79
+ "merged",
80
+ "--limit",
81
+ String(limit),
82
+ "--json",
83
+ "number,title,mergedAt,url",
84
+ ];
85
+ }
86
+
87
+ /** Is this PR merged strictly after the running build's commit? Best-effort by date. */
88
+ function isNewSinceBuild(pr: MergedPr, info: RuntimeBuildInfo): boolean {
89
+ if (!pr.mergedAt || !info.commitDate) return false;
90
+ const merged = Date.parse(pr.mergedAt);
91
+ const built = Date.parse(info.commitDate);
92
+ if (Number.isNaN(merged) || Number.isNaN(built)) return false;
93
+ return merged > built;
94
+ }
95
+
96
+ function renderHeader(info: RuntimeBuildInfo): string[] {
97
+ return [
98
+ "# Recent changes to xcsh",
99
+ "",
100
+ `Your running build: \`v${info.version}\` (commit \`${info.shortCommit || "unknown"}\`, ` +
101
+ `committed ${info.commitDate || "unknown"}). Anything merged after that is **new to you**.`,
102
+ "",
103
+ ];
104
+ }
105
+
106
+ const FOOTER = [
107
+ "",
108
+ "---",
109
+ "To confirm a change actually works, **offer to exercise it** (run the specific command/scenario",
110
+ "it touches) and report the evidence — do not claim it works from the PR title alone.",
111
+ "If you find a bug or the user proposes an improvement, classify it and **offer to file** a",
112
+ "CONTRIBUTING-compliant issue (reproduce first; no unverified claims). See `xcsh://about`.",
113
+ ];
114
+
115
+ export function renderChangesDoc(info: RuntimeBuildInfo, prs: MergedPr[], nowMs: number): string {
116
+ const lines = renderHeader(info);
117
+ if (prs.length === 0) {
118
+ lines.push("_No merged pull requests returned._");
119
+ } else {
120
+ for (const pr of prs) {
121
+ const merged = Date.parse(pr.mergedAt);
122
+ const when = Number.isNaN(merged) ? pr.mergedAt || "unknown" : formatRelativeTime(merged, nowMs);
123
+ const flag = isNewSinceBuild(pr, info) ? " — 🆕 new since your build" : "";
124
+ lines.push(`- #${pr.number} ${pr.title} (merged ${when})${flag}`);
125
+ lines.push(` ${pr.url}`);
126
+ }
127
+ }
128
+ lines.push(...FOOTER);
129
+ return lines.join("\n");
130
+ }
131
+
132
+ /** Rendered when `gh` cannot be run — actionable, never a thrown error. */
133
+ export function renderUnavailableDoc(info: RuntimeBuildInfo, slug: string, stderr: string): string {
134
+ const limit = DEFAULT_CHANGES_LIMIT;
135
+ return [
136
+ "# Recent changes to xcsh — could not query GitHub",
137
+ "",
138
+ `\`gh\` was unavailable or failed${stderr ? ` (${stderr.trim()})` : ""}.`,
139
+ "",
140
+ "Resolve recent changes manually — do not answer about new features from memory:",
141
+ "",
142
+ "```",
143
+ `gh pr list --repo ${slug} --base main --state merged --limit ${limit} --json number,title,mergedAt,url`,
144
+ "```",
145
+ "",
146
+ "Or, in a local clone of the repo:",
147
+ "",
148
+ "```",
149
+ "git log --oneline -n 20",
150
+ "```",
151
+ "",
152
+ `Your running build: \`v${info.version}\` (commit \`${info.shortCommit || "unknown"}\`).`,
153
+ ].join("\n");
154
+ }
155
+
156
+ async function defaultRunGh(args: string[]): Promise<GhResult> {
157
+ try {
158
+ const res = await $`gh ${args}`.quiet().nothrow();
159
+ return {
160
+ ok: res.exitCode === 0,
161
+ stdout: res.stdout.toString(),
162
+ stderr: res.stderr.toString(),
163
+ };
164
+ } catch (err) {
165
+ return { ok: false, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
166
+ }
167
+ }
168
+
169
+ export class ChangesResolver {
170
+ readonly #deps: ChangesDeps;
171
+
172
+ constructor(deps: ChangesDeps) {
173
+ this.#deps = deps;
174
+ }
175
+
176
+ async resolve(url: InternalUrl): Promise<InternalResource> {
177
+ const info = await this.#deps.resolveBuildInfo();
178
+ const slug = repoSlugOf(info);
179
+ const limit = parseLimit(url);
180
+
181
+ const result = await this.#deps.runGh(ghArgs(slug, limit));
182
+
183
+ let content: string;
184
+ if (!result.ok) {
185
+ content = renderUnavailableDoc(info, slug, result.stderr);
186
+ } else {
187
+ try {
188
+ const prs = parseMergedPrs(result.stdout);
189
+ content = renderChangesDoc(info, prs, this.#deps.now().getTime());
190
+ } catch (err) {
191
+ content = renderUnavailableDoc(info, slug, err instanceof Error ? err.message : String(err));
192
+ }
193
+ }
194
+
195
+ return {
196
+ url: url.href,
197
+ content,
198
+ contentType: "text/markdown",
199
+ size: Buffer.byteLength(content, "utf-8"),
200
+ sourcePath: `xcsh://changes`,
201
+ };
202
+ }
203
+ }
204
+
205
+ export function createChangesResolver(deps: Partial<ChangesDeps> = {}): ChangesResolver {
206
+ return new ChangesResolver({
207
+ resolveBuildInfo:
208
+ deps.resolveBuildInfo ?? (async () => (await import("./build-info-runtime")).getRuntimeBuildInfo()),
209
+ runGh: deps.runGh ?? defaultRunGh,
210
+ now: deps.now ?? (() => new Date()),
211
+ });
212
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Resolver for xcsh://source — "where is X implemented in my own source?".
3
+ *
4
+ * A curated capability -> source-path map plus the soft/hard editable-surface
5
+ * rule, so the agent can point at concrete files (browsable at the exact
6
+ * running commit) instead of guessing. Complements xcsh://about (identity)
7
+ * and xcsh://changes (recent history).
8
+ *
9
+ * URL form (host = "source"):
10
+ * - xcsh://source -> capability map + editable-surface classification
11
+ */
12
+ import type { RuntimeBuildInfo } from "./build-info-runtime";
13
+ import type { InternalResource, InternalUrl } from "./types";
14
+
15
+ export interface CapabilityEntry {
16
+ readonly capability: string;
17
+ readonly path: string;
18
+ readonly note: string;
19
+ }
20
+
21
+ export interface SourceDeps {
22
+ readonly resolveBuildInfo: () => Promise<RuntimeBuildInfo>;
23
+ }
24
+
25
+ /**
26
+ * Capability -> path index. Paths are repo-relative and stable; each is rendered
27
+ * as a link to the exact running commit so "read the source" lands on ground truth.
28
+ */
29
+ export const CAPABILITY_MAP: readonly CapabilityEntry[] = [
30
+ {
31
+ capability: "Identity, reflexes, and guardrails (the system prompt itself)",
32
+ path: "packages/coding-agent/src/prompts/system/system-prompt.md",
33
+ note: "The <role>, <self-awareness>, and internal-URL hint content shipped to every session.",
34
+ },
35
+ {
36
+ capability: "System-prompt assembly (composition, conditional sections)",
37
+ path: "packages/coding-agent/src/system-prompt.ts",
38
+ note: "buildSystemPrompt() renders the template against runtime data.",
39
+ },
40
+ {
41
+ capability: "xcsh:// resolver layer (all internal URLs)",
42
+ path: "packages/coding-agent/src/internal-urls/",
43
+ note: "router.ts dispatches by scheme; *-resolve.ts handle each host.",
44
+ },
45
+ {
46
+ capability: "Identity / build fingerprint (xcsh://about)",
47
+ path: "packages/coding-agent/src/internal-urls/build-info-runtime.ts",
48
+ note: "renderAboutDoc() + git-backed build info.",
49
+ },
50
+ {
51
+ capability: "Recent changes (xcsh://changes)",
52
+ path: "packages/coding-agent/src/internal-urls/changes-resolve.ts",
53
+ note: "Live merged-PR history via gh; flags what is new since your build.",
54
+ },
55
+ {
56
+ capability: "This source map (xcsh://source)",
57
+ path: "packages/coding-agent/src/internal-urls/source-resolve.ts",
58
+ note: "The file you are reading the output of.",
59
+ },
60
+ {
61
+ capability: "CLI entrypoint / compiled binary",
62
+ path: "packages/coding-agent/src/cli.ts",
63
+ note: "Bun-compiled to dist/xcsh (bun build --compile).",
64
+ },
65
+ {
66
+ capability: "Tools (bash, edit, browser, xcsh_api, catalog_workflow_runner, ...)",
67
+ path: "packages/coding-agent/src/tools/",
68
+ note: "Tool implementations and their prompt fragments.",
69
+ },
70
+ {
71
+ capability: "SE skills (account-planning, competitive, demo-components, terraform-provider, ...)",
72
+ path: ".xcsh/skills/",
73
+ note: "SKILL.md-defined capabilities loaded on demand.",
74
+ },
75
+ {
76
+ capability: "F5 XC API catalog & spec (xcsh://api-catalog, xcsh://api-spec)",
77
+ path: "packages/coding-agent/src/internal-urls/api-catalog-resolve.ts",
78
+ note: "Generated indexes sourced from api-specs-enriched.",
79
+ },
80
+ {
81
+ capability: "Console browser automation (xcsh://console, catalog_workflow_runner)",
82
+ path: "packages/coding-agent/src/internal-urls/console-resolve.ts",
83
+ note: "Deterministic UI workflows driven through the Chrome extension.",
84
+ },
85
+ {
86
+ capability: "Plugin / marketplace subsystem",
87
+ path: "packages/coding-agent/src/extensibility/plugins/marketplace/",
88
+ note: "Installs from f5-sales-demo/marketplace; see also cli/plugin-cli.ts.",
89
+ },
90
+ {
91
+ capability: "Release chain (version bump -> tag -> Homebrew/npm)",
92
+ path: "scripts/release.ts",
93
+ note: "Paired with .github/workflows/ci.yml and tag-on-version-bump.yml.",
94
+ },
95
+ {
96
+ capability: "Native addon (Rust; a HARD surface — needs a compiled release)",
97
+ path: "crates/pi-natives/",
98
+ note: "Built via packages/natives; cannot be hot-patched by a prompt/skill PR.",
99
+ },
100
+ {
101
+ capability: "Contribution process (issue -> worktree -> TDD -> PR)",
102
+ path: "CONTRIBUTING.md",
103
+ note: "The rules for any change: no unverified claims, evidence required.",
104
+ },
105
+ ];
106
+
107
+ function blobUrl(info: RuntimeBuildInfo, relPath: string): string {
108
+ const ref = info.commit || info.branch || "main";
109
+ return `${info.repoUrl}/blob/${ref}/${relPath}`;
110
+ }
111
+
112
+ export function renderSourceDoc(info: RuntimeBuildInfo): string {
113
+ const lines: string[] = [
114
+ "# xcsh source map — where capabilities live",
115
+ "",
116
+ `Repository (source of truth): ${info.repoUrl}`,
117
+ `Running build: \`v${info.version}\` (commit \`${info.shortCommit || "unknown"}\`). Links below point at that commit.`,
118
+ "",
119
+ "| Capability | Source | Note |",
120
+ "|------------|--------|------|",
121
+ ];
122
+ for (const e of CAPABILITY_MAP) {
123
+ lines.push(`| ${e.capability} | [\`${e.path}\`](${blobUrl(info, e.path)}) | ${e.note} |`);
124
+ }
125
+ lines.push(
126
+ "",
127
+ "## Editable surfaces",
128
+ "",
129
+ "- **Soft surfaces** (shippable via a normal PR — behavior changes on next release):",
130
+ " system prompt fragments, tool descriptions, internal-URL doc renderers, skills, new",
131
+ " `xcsh://` docs, keybinding/theme defaults.",
132
+ "- **Hard surfaces** (require a compiled release — cannot hot-patch):",
133
+ " the compiled binary, native Bun/Rust modules under `crates/` and `packages/*/native/`,",
134
+ " startup bootstrap, and the build-info generator.",
135
+ "",
136
+ "`~/.xcsh/` on the machine is runtime config/state — **not** xcsh's source; editing it changes nothing shipped.",
137
+ "",
138
+ "To improve xcsh: open an issue, then a PR (see `CONTRIBUTING.md`). A change is not live until a",
139
+ "release is built and installed — do not claim it is. For recent history use `xcsh://changes`;",
140
+ "for identity/version use `xcsh://about`.",
141
+ );
142
+ return lines.join("\n");
143
+ }
144
+
145
+ export class SourceResolver {
146
+ readonly #deps: SourceDeps;
147
+
148
+ constructor(deps: SourceDeps) {
149
+ this.#deps = deps;
150
+ }
151
+
152
+ async resolve(url: InternalUrl): Promise<InternalResource> {
153
+ const info = await this.#deps.resolveBuildInfo();
154
+ const content = renderSourceDoc(info);
155
+ return {
156
+ url: url.href,
157
+ content,
158
+ contentType: "text/markdown",
159
+ size: Buffer.byteLength(content, "utf-8"),
160
+ sourcePath: `xcsh://source`,
161
+ };
162
+ }
163
+ }
164
+
165
+ export function createSourceResolver(deps: Partial<SourceDeps> = {}): SourceResolver {
166
+ return new SourceResolver({
167
+ resolveBuildInfo:
168
+ deps.resolveBuildInfo ?? (async () => (await import("./build-info-runtime")).getRuntimeBuildInfo()),
169
+ });
170
+ }
@@ -38,6 +38,7 @@ import type {
38
38
  OpenAPISpec,
39
39
  } from "./api-spec-types";
40
40
  import { getRuntimeBuildInfo, type RuntimeBuildInfo, renderAboutDoc } from "./build-info-runtime";
41
+ import { type ChangesResolver, createChangesResolver } from "./changes-resolve";
41
42
  import { loadComputerProfile, renderComputerProfileMarkdown, seedComputerProfile } from "./computer-profile";
42
43
  import { type ConsoleCatalogData, EMPTY_CONSOLE_CATALOG } from "./console-catalog-types";
43
44
  import { type ConsoleFieldMetadataData, EMPTY_CONSOLE_FIELD_METADATA } from "./console-field-metadata-types";
@@ -45,6 +46,7 @@ import { type ConsoleResolver, createConsoleResolver } from "./console-resolve";
45
46
  import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
46
47
  import extensionApiContent from "./extension-api.md" with { type: "text" };
47
48
  import { createPluginResolver, type GetPluginRoots, type PluginResolver } from "./plugin-resolve";
49
+ import { createSourceResolver, type SourceResolver } from "./source-resolve";
48
50
  import { createTerraformResolver, type TerraformResolver } from "./terraform-resolve";
49
51
  import type { TerraformIndex } from "./terraform-types";
50
52
  import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
@@ -62,6 +64,8 @@ const COMPUTER_ROUTE = "computer";
62
64
  const CONSOLE_HOST = "console";
63
65
  const EXTENSION_HOST = "extension";
64
66
  const PLUGIN_HOST = "plugin";
67
+ const CHANGES_HOST = "changes";
68
+ const SOURCE_HOST = "source";
65
69
  const EMPTY_INDEX: ApiSpecIndex = { version: "unavailable", timestamp: "", domains: [] };
66
70
  const EMPTY_CATALOG_INDEX: ApiCatalogIndex = {
67
71
  version: "unavailable",
@@ -279,6 +283,8 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
279
283
  #terraformResolver: TerraformResolver | null;
280
284
  #consoleResolver: ConsoleResolver | null = null;
281
285
  #pluginResolver: PluginResolver | null = null;
286
+ #changesResolver: ChangesResolver | null = null;
287
+ #sourceResolver: SourceResolver | null = null;
282
288
  readonly #getPluginRoots: GetPluginRoots | undefined;
283
289
 
284
290
  constructor(options: InternalDocsProtocolOptions = {}) {
@@ -339,6 +345,20 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
339
345
  return this.#pluginResolver;
340
346
  }
341
347
 
348
+ #getChangesResolver(): ChangesResolver {
349
+ if (!this.#changesResolver) {
350
+ this.#changesResolver = createChangesResolver({ resolveBuildInfo: this.#resolveBuildInfo });
351
+ }
352
+ return this.#changesResolver;
353
+ }
354
+
355
+ #getSourceResolver(): SourceResolver {
356
+ if (!this.#sourceResolver) {
357
+ this.#sourceResolver = createSourceResolver({ resolveBuildInfo: this.#resolveBuildInfo });
358
+ }
359
+ return this.#sourceResolver;
360
+ }
361
+
342
362
  async resolve(url: InternalUrl): Promise<InternalResource> {
343
363
  const host = url.rawHost || url.hostname;
344
364
 
@@ -362,6 +382,14 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
362
382
  return this.#getPluginResolver().resolve(url);
363
383
  }
364
384
 
385
+ if (host === CHANGES_HOST) {
386
+ return this.#getChangesResolver().resolve(url);
387
+ }
388
+
389
+ if (host === SOURCE_HOST) {
390
+ return this.#getSourceResolver().resolve(url);
391
+ }
392
+
365
393
  if (host === BRANDING_HOST) {
366
394
  return this.#resolveBranding(url);
367
395
  }
@@ -464,6 +492,8 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
464
492
  const catalog = loadApiCatalog();
465
493
  const branding = loadBranding();
466
494
  const syntheticEntry = `- [${ABOUT_ROUTE}](${SCHEME_PREFIX}${ABOUT_ROUTE}) — identity and build fingerprint`;
495
+ const changesEntry = `- [${CHANGES_HOST}](${SCHEME_PREFIX}${CHANGES_HOST}) — recent merged PRs (what's new since your build), live via gh`;
496
+ const sourceEntry = `- [${SOURCE_HOST}](${SCHEME_PREFIX}${SOURCE_HOST}) — capability → source-path map and editable-surface rule`;
467
497
  const apiSpecEntry = `- [${API_SPEC_HOST}/](${SCHEME_PREFIX}${API_SPEC_HOST}/) — F5 XC API specifications (${specs.index.domains.length} domains, v${specs.version})`;
468
498
  const apiCatalogEntry = `- [${API_CATALOG_HOST}/](${SCHEME_PREFIX}${API_CATALOG_HOST}/) — F5 XC API operation catalog (${catalog.summaries.length} categories, v${catalog.index.version})`;
469
499
  const brandingEntry = `- [${BRANDING_HOST}](${SCHEME_PREFIX}${BRANDING_HOST}) — F5 XC branding and legacy name mapping (v${branding.version})`;
@@ -473,6 +503,8 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
473
503
  const terraformEntry = `- [${TERRAFORM_HOST}/](${SCHEME_PREFIX}${TERRAFORM_HOST}/) — F5 XC Terraform provider (${Object.keys(tf.resources).length} resources, v${tf.version})`;
474
504
  const listing = [
475
505
  syntheticEntry,
506
+ changesEntry,
507
+ sourceEntry,
476
508
  apiSpecEntry,
477
509
  apiCatalogEntry,
478
510
  brandingEntry,
@@ -481,7 +513,7 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
481
513
  computerEntry,
482
514
  ...EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](${SCHEME_PREFIX}${f})`),
483
515
  ].join("\n");
484
- const totalCount = EMBEDDED_DOC_FILENAMES.length + 7;
516
+ const totalCount = EMBEDDED_DOC_FILENAMES.length + 9;
485
517
  const content = `# Documentation\n\n${totalCount} files available:\n\n${listing}\n`;
486
518
 
487
519
  return {
@@ -28,6 +28,15 @@ technical depth exists to serve the SE work.
28
28
  Judgment: earned from production network incidents, security investigations, live
29
29
  infrastructure deployments, and customer-facing technical engagements.
30
30
 
31
+ You operate as a **GitHub / devops / security operator**: you excel at comprehensive GitHub issues
32
+ and PRs, Terraform plans, JSON manifests, architecture and how-to documentation, diagrams, and
33
+ authorized attack-traffic simulation scripts — and at organizing this netops/secops content into
34
+ GitHub repositories. You author these artifacts and file rigorously-verified issues, but you are
35
+ **not** the implementation coding harness: writing feature code for xcsh itself, the plugin
36
+ marketplace, or the API specs is delegated to a dedicated development harness (Claude Code / Codex)
37
+ with its own environment. You are also a **work in progress** under active development — you improve
38
+ by verified contribution, never by claiming. See `<self-awareness>`.
39
+
31
40
  Document your reasoning: name the assumptions you're making, state the risks you see,
32
41
  and confirm what you verified before yielding.
33
42
  Push back when warranted — especially before a demo or customer claim: state the risk,
@@ -296,6 +305,8 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
296
305
  - `xcsh://about` — Identity, version, build fingerprint, architecture, self-improvement. **MUST** read for any question about xcsh before exploring `~/.xcsh/`.
297
306
  This document contains the authoritative repository URL, issues URL, and source location.
298
307
  For the running version alone, the `<workstation>` header already has it — no tool call needed. For deeper identity (commit, branch, repo, build provenance), read `xcsh://about`. Do not call external GitHub tools or run `xcsh --version`.
308
+ - `xcsh://changes` — Recent merged PRs (what's new since your build), resolved live via `gh`. Read for "what's new / can you do X now / is Y fixed yet" — these are **never** static answers.
309
+ - `xcsh://source` — Capability → source-path map ("where is X implemented?") and the soft/hard editable-surface rule.
299
310
  - `xcsh://user` — Primary human user profile (identity, employment, contact, demographics). Read when personal identity context is needed. Do not read proactively on every turn.
300
311
  - `xcsh://user?seed=true` — Refresh profile from Salesforce, GitHub, and system sources.
301
312
  - `xcsh://computer` — Machine hardware and environment profile. Read when platform-specific recommendations needed.
@@ -313,6 +324,25 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
313
324
  {{/each}}
314
325
  {{/if}}
315
326
 
327
+ <self-awareness>
328
+ Questions about **your own** features, capabilities, recent changes, source location, or "can you do
329
+ X now / is Y fixed yet" are questions about xcsh itself — so you resolve them from ground truth, and
330
+ **never** from static prompt memory:
331
+ - Recent changes / "what's new" → read `xcsh://changes` (live merged PRs; flags what shipped after your build).
332
+ - "Where is X implemented?" → read `xcsh://source`. Deeper identity / build / version → `xcsh://about`.
333
+
334
+ You are a **work in progress** under active development, improved through verified contribution:
335
+ - When you discover a newly-shipped feature, **offer to exercise** it (run the command or scenario it
336
+ touches) and report the evidence — do not claim it works from a PR title alone.
337
+ - When you hit a self-referential bug, or the user proposes an improvement to how you work, classify it
338
+ (bug / feature / docs-drift / config) and **offer to file** a `CONTRIBUTING.md`-compliant issue:
339
+ clone the relevant repo (`f5-sales-demo/xcsh`, `f5-sales-demo/marketplace`, or
340
+ `f5-sales-demo/api-specs-enriched`), reproduce first, no unverified claims. Implementing the fix is a
341
+ job for a dedicated coding harness — your deliverable is the verified issue/PR, docs, and IaC.
342
+
343
+ Both the self-test and the filing are **offers**: act only after the user confirms.
344
+ </self-awareness>
345
+
316
346
  ### Presentation profile
317
347
 
318
348
  `catalog_workflow_runner` takes a `presentation` profile (defaults to the session setting `browser.presentation`, default `fast`):