poe-code 3.0.184 → 3.0.186
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/dist/cli/commands/configure-payload.d.ts +2 -1
- package/dist/cli/commands/configure-payload.js +4 -2
- package/dist/cli/commands/configure-payload.js.map +1 -1
- package/dist/cli/commands/configure.d.ts +1 -0
- package/dist/cli/commands/configure.js +50 -11
- package/dist/cli/commands/configure.js.map +1 -1
- package/dist/cli/commands/ensure-isolated-config.js +24 -2
- package/dist/cli/commands/ensure-isolated-config.js.map +1 -1
- package/dist/cli/commands/experiment.js +15 -2
- package/dist/cli/commands/experiment.js.map +1 -1
- package/dist/cli/commands/login.js +8 -4
- package/dist/cli/commands/login.js.map +1 -1
- package/dist/cli/commands/memory.js +16 -7
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/cli/commands/pipeline-init.js +32 -48
- package/dist/cli/commands/pipeline-init.js.map +1 -1
- package/dist/cli/commands/pipeline.js +89 -77
- package/dist/cli/commands/pipeline.js.map +1 -1
- package/dist/cli/commands/provider.d.ts +6 -0
- package/dist/cli/commands/provider.js +100 -0
- package/dist/cli/commands/provider.js.map +1 -0
- package/dist/cli/commands/shared.d.ts +7 -0
- package/dist/cli/commands/shared.js +3 -0
- package/dist/cli/commands/shared.js.map +1 -1
- package/dist/cli/commands/test.js +1 -1
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/unconfigure.js +12 -3
- package/dist/cli/commands/unconfigure.js.map +1 -1
- package/dist/cli/container.d.ts +2 -0
- package/dist/cli/container.js +3 -0
- package/dist/cli/container.js.map +1 -1
- package/dist/cli/isolated-env-runner.js +2 -2
- package/dist/cli/isolated-env-runner.js.map +1 -1
- package/dist/cli/isolated-env.d.ts +3 -2
- package/dist/cli/isolated-env.js +31 -40
- package/dist/cli/isolated-env.js.map +1 -1
- package/dist/cli/poe-code-command-runner.js +9 -2
- package/dist/cli/poe-code-command-runner.js.map +1 -1
- package/dist/cli/program.js +5 -0
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/service-registry.d.ts +7 -7
- package/dist/cli/service-registry.js.map +1 -1
- package/dist/index.js +2496 -1911
- package/dist/index.js.map +4 -4
- package/dist/providers/claude-code.d.ts +2 -1
- package/dist/providers/claude-code.js +5 -5
- package/dist/providers/claude-code.js.map +2 -2
- package/dist/providers/codex.d.ts +5 -1
- package/dist/providers/codex.js +39 -12
- package/dist/providers/codex.js.map +2 -2
- package/dist/providers/goose.d.ts +2 -1
- package/dist/providers/goose.js +24 -8
- package/dist/providers/goose.js.map +3 -3
- package/dist/providers/kimi.js +3 -3
- package/dist/providers/kimi.js.map +3 -3
- package/dist/providers/opencode.js +2 -2
- package/dist/providers/opencode.js.map +3 -3
- package/dist/providers/poe-agent.js +753 -649
- package/dist/providers/poe-agent.js.map +4 -4
- package/dist/sdk/container.js +3 -0
- package/dist/sdk/container.js.map +1 -1
- package/dist/sdk/pipeline.d.ts +1 -2
- package/dist/sdk/pipeline.js +51 -119
- package/dist/sdk/pipeline.js.map +1 -1
- package/dist/services/config.d.ts +1 -0
- package/dist/services/config.js +27 -2
- package/dist/services/config.js.map +1 -1
- package/dist/templates/pipeline/SKILL_plan.md +16 -42
- package/package.json +15 -1
- package/packages/agent-mcp-config/dist/apply.d.ts +6 -0
- package/packages/agent-mcp-config/dist/apply.js +175 -0
- package/packages/agent-mcp-config/dist/configs.d.ts +22 -0
- package/packages/agent-mcp-config/dist/configs.js +74 -0
- package/packages/agent-mcp-config/dist/index.d.ts +3 -0
- package/packages/agent-mcp-config/dist/index.js +2 -0
- package/packages/agent-mcp-config/dist/shapes.d.ts +31 -0
- package/packages/agent-mcp-config/dist/shapes.js +87 -0
- package/packages/agent-mcp-config/dist/types.d.ts +25 -0
- package/packages/agent-mcp-config/dist/types.js +1 -0
- package/packages/agent-skill-config/dist/apply.d.ts +25 -0
- package/packages/agent-skill-config/dist/apply.js +109 -0
- package/packages/agent-skill-config/dist/configs.d.ts +16 -0
- package/packages/agent-skill-config/dist/configs.js +66 -0
- package/packages/agent-skill-config/dist/exports.compile-check.d.ts +1 -0
- package/packages/agent-skill-config/dist/exports.compile-check.js +1 -0
- package/packages/agent-skill-config/dist/index.d.ts +5 -0
- package/packages/agent-skill-config/dist/index.js +2 -0
- package/packages/agent-skill-config/dist/templates/poe-generate.md +47 -0
- package/packages/agent-skill-config/dist/templates/terminal-pilot.md +45 -0
- package/packages/agent-skill-config/dist/templates.d.ts +3 -0
- package/packages/agent-skill-config/dist/templates.js +63 -0
- package/packages/agent-skill-config/dist/types.d.ts +16 -0
- package/packages/agent-skill-config/dist/types.js +1 -0
- package/packages/cmdkit/dist/cli.js +7 -2
- package/packages/cmdkit/dist/cli.js.map +2 -2
- package/packages/cmdkit-openapi/dist/api-command.d.ts +7 -0
- package/packages/cmdkit-openapi/dist/api-command.js +4 -0
- package/packages/cmdkit-openapi/dist/auth/bearer-token-auth.d.ts +8 -0
- package/packages/cmdkit-openapi/dist/auth/bearer-token-auth.js +216 -0
- package/packages/cmdkit-openapi/dist/auth/types.d.ts +9 -0
- package/packages/cmdkit-openapi/dist/auth/types.js +1 -0
- package/packages/cmdkit-openapi/dist/bin/generate.d.ts +40 -0
- package/packages/cmdkit-openapi/dist/bin/generate.js +248 -0
- package/packages/cmdkit-openapi/dist/define-client.d.ts +20 -0
- package/packages/cmdkit-openapi/dist/define-client.js +148 -0
- package/packages/cmdkit-openapi/dist/generate.d.ts +210 -0
- package/packages/cmdkit-openapi/dist/generate.js +1091 -0
- package/packages/cmdkit-openapi/dist/group-by-noun.d.ts +6 -0
- package/packages/cmdkit-openapi/dist/group-by-noun.js +17 -0
- package/packages/cmdkit-openapi/dist/http.d.ts +26 -0
- package/packages/cmdkit-openapi/dist/http.js +123 -0
- package/packages/cmdkit-openapi/dist/index.d.ts +12 -0
- package/packages/cmdkit-openapi/dist/index.js +6 -0
- package/packages/cmdkit-openapi/dist/interpreter.d.ts +6 -0
- package/packages/cmdkit-openapi/dist/interpreter.js +289 -0
- package/packages/cmdkit-openapi/dist/lock.d.ts +14 -0
- package/packages/cmdkit-openapi/dist/lock.js +48 -0
- package/packages/cmdkit-openapi/dist/naming.d.ts +24 -0
- package/packages/cmdkit-openapi/dist/naming.js +218 -0
- package/packages/cmdkit-openapi/dist/request-shape.d.ts +15 -0
- package/packages/cmdkit-openapi/dist/request-shape.js +5 -0
- package/packages/cmdkit-openapi/dist/runtime.d.ts +13 -0
- package/packages/cmdkit-openapi/dist/runtime.js +94 -0
- package/packages/cmdkit-openapi/dist/spec-source.d.ts +11 -0
- package/packages/cmdkit-openapi/dist/spec-source.js +63 -0
- package/packages/config-mutations/dist/execution/apply-mutation.d.ts +5 -0
- package/packages/config-mutations/dist/execution/apply-mutation.js +552 -0
- package/packages/config-mutations/dist/execution/path-utils.d.ts +17 -0
- package/packages/config-mutations/dist/execution/path-utils.js +58 -0
- package/packages/config-mutations/dist/execution/run-mutations.d.ts +7 -0
- package/packages/config-mutations/dist/execution/run-mutations.js +46 -0
- package/packages/config-mutations/dist/formats/index.d.ts +13 -0
- package/packages/config-mutations/dist/formats/index.js +49 -0
- package/packages/config-mutations/dist/formats/json.d.ts +31 -0
- package/packages/config-mutations/dist/formats/json.js +140 -0
- package/packages/config-mutations/dist/formats/toml.d.ts +2 -0
- package/packages/config-mutations/dist/formats/toml.js +72 -0
- package/packages/config-mutations/dist/formats/yaml.d.ts +2 -0
- package/packages/config-mutations/dist/formats/yaml.js +73 -0
- package/packages/config-mutations/dist/fs-utils.d.ts +18 -0
- package/packages/config-mutations/dist/fs-utils.js +45 -0
- package/packages/config-mutations/dist/index.d.ts +8 -0
- package/packages/config-mutations/dist/index.js +8 -0
- package/packages/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
- package/packages/config-mutations/dist/mutations/config-mutation.js +34 -0
- package/packages/config-mutations/dist/mutations/file-mutation.d.ts +52 -0
- package/packages/config-mutations/dist/mutations/file-mutation.js +46 -0
- package/packages/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
- package/packages/config-mutations/dist/mutations/template-mutation.js +32 -0
- package/packages/config-mutations/dist/template/render.d.ts +7 -0
- package/packages/config-mutations/dist/template/render.js +28 -0
- package/packages/config-mutations/dist/testing/format-utils.d.ts +7 -0
- package/packages/config-mutations/dist/testing/format-utils.js +21 -0
- package/packages/config-mutations/dist/testing/index.d.ts +3 -0
- package/packages/config-mutations/dist/testing/index.js +2 -0
- package/packages/config-mutations/dist/testing/mock-fs.d.ts +25 -0
- package/packages/config-mutations/dist/testing/mock-fs.js +170 -0
- package/packages/config-mutations/dist/types.d.ts +156 -0
- package/packages/config-mutations/dist/types.js +6 -0
- package/packages/memory/dist/audit.d.ts +11 -0
- package/packages/memory/dist/audit.js +131 -0
- package/packages/memory/dist/cache.cli.d.ts +9 -0
- package/packages/memory/dist/cache.cli.js +24 -0
- package/packages/memory/dist/cache.d.ts +14 -0
- package/packages/memory/dist/cache.js +149 -0
- package/packages/memory/dist/confidence.d.ts +4 -0
- package/packages/memory/dist/confidence.js +201 -0
- package/packages/memory/dist/corpus/001-archaeoastronomy.md +479 -0
- package/packages/memory/dist/corpus/002-magnetohydrodynamics.md +475 -0
- package/packages/memory/dist/corpus/003-biosemiotics.md +483 -0
- package/packages/memory/dist/corpus/004-cryopedology.md +483 -0
- package/packages/memory/dist/corpus/005-geomicrobiology.md +479 -0
- package/packages/memory/dist/corpus/006-aeronomy.md +487 -0
- package/packages/memory/dist/corpus/007-paleoclimatology.md +479 -0
- package/packages/memory/dist/corpus/008-hydrogeophysics.md +479 -0
- package/packages/memory/dist/corpus/009-magnetostratigraphy.md +475 -0
- package/packages/memory/dist/corpus/010-isotope-hydrology.md +481 -0
- package/packages/memory/dist/corpus/011-speleothem-geochemistry.md +474 -0
- package/packages/memory/dist/corpus/012-astrobiogeochemistry.md +475 -0
- package/packages/memory/dist/corpus/013-neuroethology.md +483 -0
- package/packages/memory/dist/corpus/014-chronophysiology.md +483 -0
- package/packages/memory/dist/corpus/015-limnogeochemistry.md +475 -0
- package/packages/memory/dist/corpus/016-palynology.md +483 -0
- package/packages/memory/dist/corpus/017-volcanotectonics.md +473 -0
- package/packages/memory/dist/corpus/018-seismotectonics.md +473 -0
- package/packages/memory/dist/corpus/019-biogeomorphology.md +475 -0
- package/packages/memory/dist/corpus/020-geobiophysics.md +479 -0
- package/packages/memory/dist/corpus/021-phytolith-analysis.md +481 -0
- package/packages/memory/dist/corpus/022-archaeometallurgy.md +479 -0
- package/packages/memory/dist/corpus/023-paleomagnetism.md +479 -0
- package/packages/memory/dist/corpus/024-biocalorimetry.md +475 -0
- package/packages/memory/dist/corpus/025-atmospheric-chemiluminescence.md +473 -0
- package/packages/memory/dist/corpus/026-cryoseismology.md +479 -0
- package/packages/memory/dist/corpus/027-extremophile-radiobiology.md +475 -0
- package/packages/memory/dist/corpus/028-heliophysics.md +479 -0
- package/packages/memory/dist/corpus/029-astroparticle-geophysics.md +474 -0
- package/packages/memory/dist/corpus/030-glaciohydrology.md +479 -0
- package/packages/memory/dist/corpus/031-permafrost-microbiology.md +477 -0
- package/packages/memory/dist/corpus/032-ecoacoustics.md +479 -0
- package/packages/memory/dist/corpus/033-dendroclimatology.md +473 -0
- package/packages/memory/dist/corpus/034-ionospheric-tomography.md +477 -0
- package/packages/memory/dist/corpus/035-marine-geodesy.md +481 -0
- package/packages/memory/dist/corpus/036-sedimentary-ancient-dna.md +481 -0
- package/packages/memory/dist/corpus/037-myrmecochory-dynamics.md +474 -0
- package/packages/memory/dist/corpus/038-chemosensory-ecology.md +477 -0
- package/packages/memory/dist/corpus/039-spintronics-materials.md +479 -0
- package/packages/memory/dist/corpus/040-nanotoxicology.md +483 -0
- package/packages/memory/dist/corpus/041-cosmochemistry.md +483 -0
- package/packages/memory/dist/corpus/042-quaternary-geochronology.md +471 -0
- package/packages/memory/dist/corpus/043-biophotonics.md +479 -0
- package/packages/memory/dist/corpus/044-evolutionary-morphometrics.md +481 -0
- package/packages/memory/dist/corpus/045-cryovolcanology.md +475 -0
- package/packages/memory/dist/corpus/046-exoplanet-atmospheric-dynamics.md +479 -0
- package/packages/memory/dist/corpus/047-microbial-electrosynthesis.md +477 -0
- package/packages/memory/dist/corpus/048-paleoseismology.md +479 -0
- package/packages/memory/dist/corpus/049-actinide-geochemistry.md +477 -0
- package/packages/memory/dist/corpus/050-quantum-biology.md +489 -0
- package/packages/memory/dist/edit.d.ts +10 -0
- package/packages/memory/dist/edit.js +43 -0
- package/packages/memory/dist/explain.cli.d.ts +8 -0
- package/packages/memory/dist/explain.cli.js +9 -0
- package/packages/memory/dist/explain.d.ts +8 -0
- package/packages/memory/dist/explain.js +77 -0
- package/packages/memory/dist/frontmatter.d.ts +9 -0
- package/packages/memory/dist/frontmatter.js +217 -0
- package/packages/memory/dist/index.d.ts +21 -0
- package/packages/memory/dist/index.js +4807 -0
- package/packages/memory/dist/index.js.map +7 -0
- package/packages/memory/dist/ingest.d.ts +3 -0
- package/packages/memory/dist/ingest.js +118 -0
- package/packages/memory/dist/init.d.ts +2 -0
- package/packages/memory/dist/init.js +24 -0
- package/packages/memory/dist/install.d.ts +18 -0
- package/packages/memory/dist/install.js +50 -0
- package/packages/memory/dist/lock.d.ts +19 -0
- package/packages/memory/dist/lock.js +102 -0
- package/packages/memory/dist/mcp.d.ts +7 -0
- package/packages/memory/dist/mcp.js +58 -0
- package/packages/memory/dist/pages.d.ts +4 -0
- package/packages/memory/dist/pages.js +92 -0
- package/packages/memory/dist/paths.d.ts +12 -0
- package/packages/memory/dist/paths.js +34 -0
- package/packages/memory/dist/query.d.ts +10 -0
- package/packages/memory/dist/query.js +130 -0
- package/packages/memory/dist/reconcile.d.ts +9 -0
- package/packages/memory/dist/reconcile.js +138 -0
- package/packages/memory/dist/resolve-root.d.ts +11 -0
- package/packages/memory/dist/resolve-root.js +22 -0
- package/packages/memory/dist/search.d.ts +2 -0
- package/packages/memory/dist/search.js +29 -0
- package/packages/memory/dist/status.d.ts +7 -0
- package/packages/memory/dist/status.js +46 -0
- package/packages/memory/dist/tokens.d.ts +2 -0
- package/packages/memory/dist/tokens.js +71 -0
- package/packages/memory/dist/types.d.ts +155 -0
- package/packages/memory/dist/types.js +1 -0
- package/packages/memory/dist/write.d.ts +9 -0
- package/packages/memory/dist/write.js +76 -0
|
@@ -0,0 +1,4807 @@
|
|
|
1
|
+
// packages/memory/src/paths.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
var MEMORY_INDEX_RELPATH = "INDEX.md";
|
|
4
|
+
var MEMORY_LOG_RELPATH = "LOG.md";
|
|
5
|
+
var MEMORY_LOCK_RELPATH = ".lock";
|
|
6
|
+
var MEMORY_PAGES_DIR_RELPATH = "pages";
|
|
7
|
+
var MEMORY_CACHE_DIR_RELPATH = ".cache";
|
|
8
|
+
var MEMORY_INGEST_CACHE_DIR_RELPATH = `${MEMORY_CACHE_DIR_RELPATH}/ingest`;
|
|
9
|
+
var MemoryPathError = class extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "MemoryPathError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function resolveMemoryRoot(cwd) {
|
|
16
|
+
return path.resolve(cwd, ".poe-code", "memory");
|
|
17
|
+
}
|
|
18
|
+
function assertSafeRelPath(input) {
|
|
19
|
+
const trimmed = input.trim();
|
|
20
|
+
if (trimmed.length === 0) {
|
|
21
|
+
throw new MemoryPathError("Expected a non-empty relative path.");
|
|
22
|
+
}
|
|
23
|
+
const slashNormalized = trimmed.replaceAll("\\", "/");
|
|
24
|
+
if (path.posix.isAbsolute(slashNormalized) || path.win32.isAbsolute(slashNormalized)) {
|
|
25
|
+
throw new MemoryPathError(`Expected a relative path, received absolute path "${input}".`);
|
|
26
|
+
}
|
|
27
|
+
const normalized = path.posix.normalize(slashNormalized);
|
|
28
|
+
if (normalized === "." || normalized.length === 0) {
|
|
29
|
+
throw new MemoryPathError("Expected a relative path to a file or directory.");
|
|
30
|
+
}
|
|
31
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
32
|
+
throw new MemoryPathError(`Relative path "${input}" cannot escape the memory root.`);
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// packages/memory/src/resolve-root.ts
|
|
38
|
+
import path8 from "node:path";
|
|
39
|
+
|
|
40
|
+
// packages/poe-code-config/src/schema.ts
|
|
41
|
+
function defineScope(scope, schema) {
|
|
42
|
+
return {
|
|
43
|
+
scope,
|
|
44
|
+
schema
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// packages/poe-code-config/src/plan-scope.ts
|
|
49
|
+
var planConfigScope = defineScope("plan", {
|
|
50
|
+
plan_directory: {
|
|
51
|
+
type: "string",
|
|
52
|
+
default: "docs/plans",
|
|
53
|
+
env: "POE_PLAN_DIRECTORY",
|
|
54
|
+
doc: "Directory where planning documents are stored"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// packages/poe-code-config/src/store.ts
|
|
59
|
+
import path6 from "node:path";
|
|
60
|
+
|
|
61
|
+
// packages/config-extends/src/discover.ts
|
|
62
|
+
import path2 from "node:path";
|
|
63
|
+
async function findBase(name, bases, fs13) {
|
|
64
|
+
const checkedPaths = [];
|
|
65
|
+
for (const basePath of bases) {
|
|
66
|
+
for (const extension of [".md", ".yaml", ".yml", ".json"]) {
|
|
67
|
+
const filePath = path2.join(basePath, `${name}${extension}`);
|
|
68
|
+
checkedPaths.push(filePath);
|
|
69
|
+
try {
|
|
70
|
+
return {
|
|
71
|
+
content: await fs13.readFile(filePath, "utf8"),
|
|
72
|
+
filePath
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (hasCode(error, "ENOENT")) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Base "${name}" not found.
|
|
83
|
+
Checked paths:
|
|
84
|
+
- ${checkedPaths.join("\n- ")}`);
|
|
85
|
+
}
|
|
86
|
+
function hasCode(error, code) {
|
|
87
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// packages/config-extends/src/parse.ts
|
|
91
|
+
import path3 from "node:path";
|
|
92
|
+
import matter from "gray-matter";
|
|
93
|
+
import { parse as parseYaml } from "yaml";
|
|
94
|
+
function parseDocument(content, filePath) {
|
|
95
|
+
const normalizedContent = stripBom(content);
|
|
96
|
+
const format = detectFormat(normalizedContent, filePath);
|
|
97
|
+
const data = format === "markdown" ? parseMarkdown(normalizedContent) : toData(format === "json" ? JSON.parse(normalizedContent) : parseYaml(normalizedContent));
|
|
98
|
+
const hasExtendsField = Object.hasOwn(data, "extends");
|
|
99
|
+
const extendsValue = data.extends === true;
|
|
100
|
+
delete data.extends;
|
|
101
|
+
return {
|
|
102
|
+
data,
|
|
103
|
+
format,
|
|
104
|
+
extends: extendsValue,
|
|
105
|
+
hasExtendsField
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function detectFormat(content, filePath) {
|
|
109
|
+
const extension = path3.extname(filePath).toLowerCase();
|
|
110
|
+
if (extension === ".md") {
|
|
111
|
+
return "markdown";
|
|
112
|
+
}
|
|
113
|
+
if (extension === ".yaml" || extension === ".yml") {
|
|
114
|
+
return "yaml";
|
|
115
|
+
}
|
|
116
|
+
if (extension === ".json") {
|
|
117
|
+
return "json";
|
|
118
|
+
}
|
|
119
|
+
if (content.startsWith("{")) {
|
|
120
|
+
return "json";
|
|
121
|
+
}
|
|
122
|
+
if (content.startsWith("---\n") || content.startsWith("---\r\n")) {
|
|
123
|
+
return "markdown";
|
|
124
|
+
}
|
|
125
|
+
return "yaml";
|
|
126
|
+
}
|
|
127
|
+
function parseMarkdown(content) {
|
|
128
|
+
const document = matter(content);
|
|
129
|
+
return {
|
|
130
|
+
...toData(document.data),
|
|
131
|
+
prompt: document.content
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function toData(value) {
|
|
135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
return { ...value };
|
|
139
|
+
}
|
|
140
|
+
function stripBom(content) {
|
|
141
|
+
if (!content.startsWith("\uFEFF")) {
|
|
142
|
+
return content;
|
|
143
|
+
}
|
|
144
|
+
return content.slice(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// packages/config-extends/src/merge.ts
|
|
148
|
+
function mergeLayers(layers) {
|
|
149
|
+
return mergeObjectLayers(layers, []);
|
|
150
|
+
}
|
|
151
|
+
function mergeObjectLayers(layers, path25) {
|
|
152
|
+
const data = {};
|
|
153
|
+
const sources = {};
|
|
154
|
+
for (const key of collectKeys(layers)) {
|
|
155
|
+
const resolved = resolveKey(layers, key, path25);
|
|
156
|
+
if (resolved === void 0) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
data[key] = resolved.value;
|
|
160
|
+
Object.assign(sources, resolved.sources);
|
|
161
|
+
}
|
|
162
|
+
return { data, sources };
|
|
163
|
+
}
|
|
164
|
+
function collectKeys(layers) {
|
|
165
|
+
const keys = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const layer of layers) {
|
|
167
|
+
for (const key of Object.keys(layer.data)) {
|
|
168
|
+
keys.add(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return [...keys];
|
|
172
|
+
}
|
|
173
|
+
function resolveKey(layers, key, path25) {
|
|
174
|
+
let winningSource;
|
|
175
|
+
let winningValue;
|
|
176
|
+
const objectLayers = [];
|
|
177
|
+
for (const layer of layers) {
|
|
178
|
+
const candidate = layer.data[key];
|
|
179
|
+
if (!isWinningCandidate(key, candidate)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (winningSource === void 0) {
|
|
183
|
+
winningSource = layer.source;
|
|
184
|
+
winningValue = candidate;
|
|
185
|
+
if (isPlainObject(candidate)) {
|
|
186
|
+
objectLayers.push({
|
|
187
|
+
source: layer.source,
|
|
188
|
+
data: candidate
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (isPlainObject(winningValue) && isPlainObject(candidate)) {
|
|
194
|
+
objectLayers.push({
|
|
195
|
+
source: layer.source,
|
|
196
|
+
data: candidate
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (winningSource === void 0) {
|
|
201
|
+
return void 0;
|
|
202
|
+
}
|
|
203
|
+
const fullPath = buildPath(path25, key);
|
|
204
|
+
if (isPlainObject(winningValue)) {
|
|
205
|
+
const merged = mergeObjectLayers(objectLayers, [...path25, key]);
|
|
206
|
+
return {
|
|
207
|
+
value: merged.data,
|
|
208
|
+
sources: {
|
|
209
|
+
[fullPath]: winningSource,
|
|
210
|
+
...merged.sources
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
value: cloneValue(winningValue),
|
|
216
|
+
sources: {
|
|
217
|
+
[fullPath]: winningSource
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function isWinningCandidate(key, value) {
|
|
222
|
+
if (value === void 0) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (key === "prompt" && value === "") {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function buildPath(path25, key) {
|
|
231
|
+
return [...path25, key].join(".");
|
|
232
|
+
}
|
|
233
|
+
function isPlainObject(value) {
|
|
234
|
+
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const prototype = Object.getPrototypeOf(value);
|
|
238
|
+
return prototype === Object.prototype || prototype === null;
|
|
239
|
+
}
|
|
240
|
+
function cloneValue(value) {
|
|
241
|
+
if (Array.isArray(value)) {
|
|
242
|
+
return value.map((entry) => cloneValue(entry));
|
|
243
|
+
}
|
|
244
|
+
if (!isPlainObject(value)) {
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
const clone = Object.create(Object.getPrototypeOf(value));
|
|
248
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
249
|
+
clone[key] = cloneValue(entry);
|
|
250
|
+
}
|
|
251
|
+
return clone;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// packages/config-extends/src/resolve.ts
|
|
255
|
+
import path4 from "node:path";
|
|
256
|
+
var MAX_EXTENDS_DEPTH = 5;
|
|
257
|
+
var YIELD_TOKEN = "{{yield}}";
|
|
258
|
+
async function resolve(chain, options) {
|
|
259
|
+
const { baseLayers, documentIndex, documentLayer } = classifyChain(chain);
|
|
260
|
+
const parsedDocument = parseDocument(documentLayer.content, documentLayer.filePath);
|
|
261
|
+
const resolvedBase = shouldResolveBase(parsedDocument, options.autoExtend) ? await resolveBaseChain({
|
|
262
|
+
name: documentLayer.baseName ?? getBaseName(documentLayer.filePath),
|
|
263
|
+
baseLayers,
|
|
264
|
+
options,
|
|
265
|
+
optional: !parsedDocument.extends,
|
|
266
|
+
visited: /* @__PURE__ */ new Set([documentLayer.filePath]),
|
|
267
|
+
depth: 1
|
|
268
|
+
}) : void 0;
|
|
269
|
+
const composedPrompt = composePromptChain(
|
|
270
|
+
{
|
|
271
|
+
source: documentLayer.source,
|
|
272
|
+
data: parsedDocument.data
|
|
273
|
+
},
|
|
274
|
+
resolvedBase?.layers ?? []
|
|
275
|
+
);
|
|
276
|
+
const merged = mergeLayers([
|
|
277
|
+
...collectDataLayers(chain.slice(0, documentIndex)),
|
|
278
|
+
{
|
|
279
|
+
source: documentLayer.source,
|
|
280
|
+
data: withResolvedPrompt(parsedDocument.data, composedPrompt?.prompt)
|
|
281
|
+
},
|
|
282
|
+
...stripResolvedBasePrompts(resolvedBase?.layers ?? [], composedPrompt?.consumedBaseIndexes ?? /* @__PURE__ */ new Set()),
|
|
283
|
+
...collectDataLayers(chain.slice(documentIndex + 1))
|
|
284
|
+
]);
|
|
285
|
+
if (composedPrompt !== void 0 && merged.sources.prompt === documentLayer.source && composedPrompt.source !== void 0) {
|
|
286
|
+
merged.sources.prompt = composedPrompt.source;
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
data: merged.data,
|
|
290
|
+
sources: merged.sources,
|
|
291
|
+
chain: [documentLayer.filePath, ...resolvedBase?.chain ?? []]
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function classifyChain(chain) {
|
|
295
|
+
const baseLayers = [];
|
|
296
|
+
const documentLayers = [];
|
|
297
|
+
for (const [index, layer] of chain.entries()) {
|
|
298
|
+
if (isDataLayer(layer)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (isDocumentLayer(layer)) {
|
|
302
|
+
documentLayers.push({ index, layer });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (isBaseLayer(layer)) {
|
|
306
|
+
baseLayers.push(layer);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (documentLayers.length !== 1) {
|
|
310
|
+
throw new Error(`Exactly one document layer is required, received ${documentLayers.length}.`);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
baseLayers,
|
|
314
|
+
documentIndex: documentLayers[0].index,
|
|
315
|
+
documentLayer: documentLayers[0].layer
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
async function resolveBaseChain({
|
|
319
|
+
name,
|
|
320
|
+
baseLayers,
|
|
321
|
+
options,
|
|
322
|
+
optional,
|
|
323
|
+
visited,
|
|
324
|
+
depth
|
|
325
|
+
}) {
|
|
326
|
+
if (depth > MAX_EXTENDS_DEPTH) {
|
|
327
|
+
throw new Error(`Maximum extends depth exceeded (${MAX_EXTENDS_DEPTH}).`);
|
|
328
|
+
}
|
|
329
|
+
let discoveredBase;
|
|
330
|
+
try {
|
|
331
|
+
discoveredBase = await findBase(
|
|
332
|
+
name,
|
|
333
|
+
baseLayers.map((layer) => layer.path),
|
|
334
|
+
options.fs
|
|
335
|
+
);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (optional && isBaseNotFoundError(error)) {
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
if (visited.has(discoveredBase.filePath)) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`Circular extends detected.
|
|
345
|
+
Visited files:
|
|
346
|
+
- ${[...visited, discoveredBase.filePath].join("\n- ")}`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
const matchedBaseIndex = baseLayers.findIndex(
|
|
350
|
+
(layer) => layer.path === path4.dirname(discoveredBase.filePath)
|
|
351
|
+
);
|
|
352
|
+
if (matchedBaseIndex === -1) {
|
|
353
|
+
throw new Error(`Resolved base is outside configured base paths: ${discoveredBase.filePath}`);
|
|
354
|
+
}
|
|
355
|
+
const parsedBase = parseDocument(discoveredBase.content, discoveredBase.filePath);
|
|
356
|
+
const nextVisited = new Set(visited);
|
|
357
|
+
nextVisited.add(discoveredBase.filePath);
|
|
358
|
+
const nestedBase = parsedBase.extends ? await resolveBaseChain({
|
|
359
|
+
name: getBaseName(discoveredBase.filePath),
|
|
360
|
+
baseLayers: baseLayers.slice(matchedBaseIndex + 1),
|
|
361
|
+
options,
|
|
362
|
+
optional: false,
|
|
363
|
+
visited: nextVisited,
|
|
364
|
+
depth: depth + 1
|
|
365
|
+
}) : void 0;
|
|
366
|
+
return {
|
|
367
|
+
layers: [
|
|
368
|
+
{
|
|
369
|
+
source: baseLayers[matchedBaseIndex].source,
|
|
370
|
+
data: parsedBase.data
|
|
371
|
+
},
|
|
372
|
+
...nestedBase?.layers ?? []
|
|
373
|
+
],
|
|
374
|
+
chain: [discoveredBase.filePath, ...nestedBase?.chain ?? []]
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function collectDataLayers(chain) {
|
|
378
|
+
return chain.filter(isDataLayer);
|
|
379
|
+
}
|
|
380
|
+
function composePromptChain(documentLayer, baseLayers) {
|
|
381
|
+
const documentPrompt = documentLayer.data.prompt;
|
|
382
|
+
if (documentPrompt !== void 0 && typeof documentPrompt !== "string") {
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
if (documentPrompt !== void 0) {
|
|
386
|
+
assertValidYieldCount(documentPrompt);
|
|
387
|
+
}
|
|
388
|
+
let prompt = documentPrompt;
|
|
389
|
+
let source = prompt === void 0 || prompt === "" ? void 0 : documentLayer.source;
|
|
390
|
+
const consumedBaseIndexes = /* @__PURE__ */ new Set();
|
|
391
|
+
for (const [index, layer] of baseLayers.entries()) {
|
|
392
|
+
const candidate = layer.data.prompt;
|
|
393
|
+
if (candidate === void 0) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (typeof candidate !== "string") {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
assertValidYieldCount(candidate);
|
|
400
|
+
consumedBaseIndexes.add(index);
|
|
401
|
+
prompt = composeAdjacentPrompts(prompt, candidate);
|
|
402
|
+
if (source === void 0 && candidate !== "") {
|
|
403
|
+
source = layer.source;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (prompt !== void 0 && prompt.includes(YIELD_TOKEN)) {
|
|
407
|
+
throw new Error('Final resolved prompt contains an unresolved "{{yield}}" token.');
|
|
408
|
+
}
|
|
409
|
+
if (prompt === void 0) {
|
|
410
|
+
return void 0;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
consumedBaseIndexes,
|
|
414
|
+
prompt,
|
|
415
|
+
source
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function composeAdjacentPrompts(high, low) {
|
|
419
|
+
if (high === void 0 || high === "") {
|
|
420
|
+
return low.includes(YIELD_TOKEN) ? replaceYield(low, "") : low;
|
|
421
|
+
}
|
|
422
|
+
if (high.includes(YIELD_TOKEN)) {
|
|
423
|
+
return replaceYield(high, low);
|
|
424
|
+
}
|
|
425
|
+
if (low.includes(YIELD_TOKEN)) {
|
|
426
|
+
return replaceYield(low, high);
|
|
427
|
+
}
|
|
428
|
+
return high;
|
|
429
|
+
}
|
|
430
|
+
function replaceYield(prompt, replacement) {
|
|
431
|
+
return prompt.replace(YIELD_TOKEN, replacement);
|
|
432
|
+
}
|
|
433
|
+
function assertValidYieldCount(prompt) {
|
|
434
|
+
if (countYieldTokens(prompt) > 1) {
|
|
435
|
+
throw new Error('Prompt composition supports exactly one "{{yield}}" token per prompt.');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function countYieldTokens(prompt) {
|
|
439
|
+
return prompt.split(YIELD_TOKEN).length - 1;
|
|
440
|
+
}
|
|
441
|
+
function withResolvedPrompt(data, prompt) {
|
|
442
|
+
if (prompt === void 0) {
|
|
443
|
+
return data;
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
...data,
|
|
447
|
+
prompt
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function stripResolvedBasePrompts(layers, consumedBaseIndexes) {
|
|
451
|
+
return layers.map((layer, index) => {
|
|
452
|
+
if (!consumedBaseIndexes.has(index) || typeof layer.data.prompt !== "string") {
|
|
453
|
+
return layer;
|
|
454
|
+
}
|
|
455
|
+
const { prompt: ignoredPrompt, ...data } = layer.data;
|
|
456
|
+
void ignoredPrompt;
|
|
457
|
+
return {
|
|
458
|
+
source: layer.source,
|
|
459
|
+
data
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function getBaseName(filePath) {
|
|
464
|
+
return path4.basename(filePath, path4.extname(filePath));
|
|
465
|
+
}
|
|
466
|
+
function shouldResolveBase(parsedDocument, autoExtend) {
|
|
467
|
+
return parsedDocument.extends || autoExtend === true && !parsedDocument.hasExtendsField;
|
|
468
|
+
}
|
|
469
|
+
function isBaseNotFoundError(error) {
|
|
470
|
+
return error instanceof Error && error.message.startsWith('Base "') && error.message.includes('" not found.\nChecked paths:');
|
|
471
|
+
}
|
|
472
|
+
function isDataLayer(layer) {
|
|
473
|
+
return "data" in layer;
|
|
474
|
+
}
|
|
475
|
+
function isDocumentLayer(layer) {
|
|
476
|
+
return "filePath" in layer && "content" in layer;
|
|
477
|
+
}
|
|
478
|
+
function isBaseLayer(layer) {
|
|
479
|
+
return "path" in layer;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// packages/config-mutations/src/mutations/config-mutation.ts
|
|
483
|
+
function merge(options) {
|
|
484
|
+
return {
|
|
485
|
+
kind: "configMerge",
|
|
486
|
+
target: options.target,
|
|
487
|
+
value: options.value,
|
|
488
|
+
format: options.format,
|
|
489
|
+
pruneByPrefix: options.pruneByPrefix,
|
|
490
|
+
label: options.label
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function prune(options) {
|
|
494
|
+
return {
|
|
495
|
+
kind: "configPrune",
|
|
496
|
+
target: options.target,
|
|
497
|
+
shape: options.shape,
|
|
498
|
+
format: options.format,
|
|
499
|
+
onlyIf: options.onlyIf,
|
|
500
|
+
label: options.label
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function transform(options) {
|
|
504
|
+
return {
|
|
505
|
+
kind: "configTransform",
|
|
506
|
+
target: options.target,
|
|
507
|
+
format: options.format,
|
|
508
|
+
transform: options.transform,
|
|
509
|
+
label: options.label
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
var configMutation = {
|
|
513
|
+
merge,
|
|
514
|
+
prune,
|
|
515
|
+
transform
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// packages/config-mutations/src/mutations/file-mutation.ts
|
|
519
|
+
function ensureDirectory(options) {
|
|
520
|
+
return {
|
|
521
|
+
kind: "ensureDirectory",
|
|
522
|
+
path: options.path,
|
|
523
|
+
label: options.label
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function remove(options) {
|
|
527
|
+
return {
|
|
528
|
+
kind: "removeFile",
|
|
529
|
+
target: options.target,
|
|
530
|
+
whenEmpty: options.whenEmpty,
|
|
531
|
+
whenContentMatches: options.whenContentMatches,
|
|
532
|
+
label: options.label
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function removeDirectory(options) {
|
|
536
|
+
return {
|
|
537
|
+
kind: "removeDirectory",
|
|
538
|
+
path: options.path,
|
|
539
|
+
force: options.force,
|
|
540
|
+
label: options.label
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function chmod(options) {
|
|
544
|
+
return {
|
|
545
|
+
kind: "chmod",
|
|
546
|
+
target: options.target,
|
|
547
|
+
mode: options.mode,
|
|
548
|
+
label: options.label
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function backup(options) {
|
|
552
|
+
return {
|
|
553
|
+
kind: "backup",
|
|
554
|
+
target: options.target,
|
|
555
|
+
label: options.label
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var fileMutation = {
|
|
559
|
+
ensureDirectory,
|
|
560
|
+
remove,
|
|
561
|
+
removeDirectory,
|
|
562
|
+
chmod,
|
|
563
|
+
backup
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// packages/config-mutations/src/mutations/template-mutation.ts
|
|
567
|
+
function write(options) {
|
|
568
|
+
return {
|
|
569
|
+
kind: "templateWrite",
|
|
570
|
+
target: options.target,
|
|
571
|
+
templateId: options.templateId,
|
|
572
|
+
context: options.context,
|
|
573
|
+
label: options.label
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function mergeToml(options) {
|
|
577
|
+
return {
|
|
578
|
+
kind: "templateMergeToml",
|
|
579
|
+
target: options.target,
|
|
580
|
+
templateId: options.templateId,
|
|
581
|
+
context: options.context,
|
|
582
|
+
label: options.label
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function mergeJson(options) {
|
|
586
|
+
return {
|
|
587
|
+
kind: "templateMergeJson",
|
|
588
|
+
target: options.target,
|
|
589
|
+
templateId: options.templateId,
|
|
590
|
+
context: options.context,
|
|
591
|
+
label: options.label
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
var templateMutation = {
|
|
595
|
+
write,
|
|
596
|
+
mergeToml,
|
|
597
|
+
mergeJson
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// packages/config-mutations/src/execution/apply-mutation.ts
|
|
601
|
+
import Mustache from "mustache";
|
|
602
|
+
|
|
603
|
+
// packages/config-mutations/src/formats/json.ts
|
|
604
|
+
import * as jsonc from "jsonc-parser";
|
|
605
|
+
function isConfigObject(value) {
|
|
606
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
607
|
+
}
|
|
608
|
+
function parse2(content) {
|
|
609
|
+
if (!content || content.trim() === "") {
|
|
610
|
+
return {};
|
|
611
|
+
}
|
|
612
|
+
const errors = [];
|
|
613
|
+
const parsed = jsonc.parse(content, errors, {
|
|
614
|
+
allowTrailingComma: true,
|
|
615
|
+
disallowComments: false
|
|
616
|
+
});
|
|
617
|
+
if (errors.length > 0) {
|
|
618
|
+
throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
|
|
619
|
+
}
|
|
620
|
+
if (parsed === null || parsed === void 0) {
|
|
621
|
+
return {};
|
|
622
|
+
}
|
|
623
|
+
if (!isConfigObject(parsed)) {
|
|
624
|
+
throw new Error("Expected JSON object.");
|
|
625
|
+
}
|
|
626
|
+
return parsed;
|
|
627
|
+
}
|
|
628
|
+
function serialize(obj) {
|
|
629
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
function merge2(base, patch) {
|
|
633
|
+
const result = { ...base };
|
|
634
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
635
|
+
if (value === void 0) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const existing = result[key];
|
|
639
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
640
|
+
result[key] = merge2(existing, value);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
result[key] = value;
|
|
644
|
+
}
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
function prune2(obj, shape) {
|
|
648
|
+
let changed = false;
|
|
649
|
+
const result = { ...obj };
|
|
650
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
651
|
+
if (!(key in result)) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const current = result[key];
|
|
655
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
656
|
+
delete result[key];
|
|
657
|
+
changed = true;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
661
|
+
const { changed: childChanged, result: childResult } = prune2(
|
|
662
|
+
current,
|
|
663
|
+
pattern
|
|
664
|
+
);
|
|
665
|
+
if (childChanged) {
|
|
666
|
+
changed = true;
|
|
667
|
+
}
|
|
668
|
+
if (Object.keys(childResult).length === 0) {
|
|
669
|
+
delete result[key];
|
|
670
|
+
} else {
|
|
671
|
+
result[key] = childResult;
|
|
672
|
+
}
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
delete result[key];
|
|
676
|
+
changed = true;
|
|
677
|
+
}
|
|
678
|
+
return { changed, result };
|
|
679
|
+
}
|
|
680
|
+
var jsonFormat = {
|
|
681
|
+
parse: parse2,
|
|
682
|
+
serialize,
|
|
683
|
+
merge: merge2,
|
|
684
|
+
prune: prune2
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// packages/config-mutations/src/formats/toml.ts
|
|
688
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
689
|
+
function isConfigObject2(value) {
|
|
690
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
691
|
+
}
|
|
692
|
+
function parse3(content) {
|
|
693
|
+
if (!content || content.trim() === "") {
|
|
694
|
+
return {};
|
|
695
|
+
}
|
|
696
|
+
const parsed = parseToml(content);
|
|
697
|
+
if (!isConfigObject2(parsed)) {
|
|
698
|
+
throw new Error("Expected TOML document to be a table.");
|
|
699
|
+
}
|
|
700
|
+
return parsed;
|
|
701
|
+
}
|
|
702
|
+
function serialize2(obj) {
|
|
703
|
+
const serialized = stringifyToml(obj);
|
|
704
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}
|
|
705
|
+
`;
|
|
706
|
+
}
|
|
707
|
+
function merge3(base, patch) {
|
|
708
|
+
const result = { ...base };
|
|
709
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
710
|
+
if (value === void 0) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const existing = result[key];
|
|
714
|
+
if (isConfigObject2(existing) && isConfigObject2(value)) {
|
|
715
|
+
result[key] = merge3(existing, value);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
result[key] = value;
|
|
719
|
+
}
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
function prune3(obj, shape) {
|
|
723
|
+
let changed = false;
|
|
724
|
+
const result = { ...obj };
|
|
725
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
726
|
+
if (!(key in result)) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const current = result[key];
|
|
730
|
+
if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
|
|
731
|
+
delete result[key];
|
|
732
|
+
changed = true;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (isConfigObject2(pattern) && isConfigObject2(current)) {
|
|
736
|
+
const { changed: childChanged, result: childResult } = prune3(
|
|
737
|
+
current,
|
|
738
|
+
pattern
|
|
739
|
+
);
|
|
740
|
+
if (childChanged) {
|
|
741
|
+
changed = true;
|
|
742
|
+
}
|
|
743
|
+
if (Object.keys(childResult).length === 0) {
|
|
744
|
+
delete result[key];
|
|
745
|
+
} else {
|
|
746
|
+
result[key] = childResult;
|
|
747
|
+
}
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
delete result[key];
|
|
751
|
+
changed = true;
|
|
752
|
+
}
|
|
753
|
+
return { changed, result };
|
|
754
|
+
}
|
|
755
|
+
var tomlFormat = {
|
|
756
|
+
parse: parse3,
|
|
757
|
+
serialize: serialize2,
|
|
758
|
+
merge: merge3,
|
|
759
|
+
prune: prune3
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// packages/config-mutations/src/formats/yaml.ts
|
|
763
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
764
|
+
function isConfigObject3(value) {
|
|
765
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
766
|
+
}
|
|
767
|
+
function parse4(content) {
|
|
768
|
+
if (!content || content.trim() === "") {
|
|
769
|
+
return {};
|
|
770
|
+
}
|
|
771
|
+
const parsed = parseYaml2(content);
|
|
772
|
+
if (parsed === null || parsed === void 0) {
|
|
773
|
+
return {};
|
|
774
|
+
}
|
|
775
|
+
if (!isConfigObject3(parsed)) {
|
|
776
|
+
throw new Error("Expected YAML object.");
|
|
777
|
+
}
|
|
778
|
+
return parsed;
|
|
779
|
+
}
|
|
780
|
+
function serialize3(obj) {
|
|
781
|
+
const serialized = stringifyYaml(obj);
|
|
782
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}
|
|
783
|
+
`;
|
|
784
|
+
}
|
|
785
|
+
function merge4(base, patch) {
|
|
786
|
+
const result = { ...base };
|
|
787
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
788
|
+
if (value === void 0) {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const existing = result[key];
|
|
792
|
+
if (isConfigObject3(existing) && isConfigObject3(value)) {
|
|
793
|
+
result[key] = merge4(existing, value);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
result[key] = value;
|
|
797
|
+
}
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
function prune4(obj, shape) {
|
|
801
|
+
let changed = false;
|
|
802
|
+
const result = { ...obj };
|
|
803
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
804
|
+
if (!(key in result)) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const current = result[key];
|
|
808
|
+
if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
|
|
809
|
+
delete result[key];
|
|
810
|
+
changed = true;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (isConfigObject3(pattern) && isConfigObject3(current)) {
|
|
814
|
+
const { changed: childChanged, result: childResult } = prune4(current, pattern);
|
|
815
|
+
if (childChanged) {
|
|
816
|
+
changed = true;
|
|
817
|
+
}
|
|
818
|
+
if (Object.keys(childResult).length === 0) {
|
|
819
|
+
delete result[key];
|
|
820
|
+
} else {
|
|
821
|
+
result[key] = childResult;
|
|
822
|
+
}
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
delete result[key];
|
|
826
|
+
changed = true;
|
|
827
|
+
}
|
|
828
|
+
return { changed, result };
|
|
829
|
+
}
|
|
830
|
+
var yamlFormat = {
|
|
831
|
+
parse: parse4,
|
|
832
|
+
serialize: serialize3,
|
|
833
|
+
merge: merge4,
|
|
834
|
+
prune: prune4
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// packages/config-mutations/src/formats/index.ts
|
|
838
|
+
var formatRegistry = {
|
|
839
|
+
json: jsonFormat,
|
|
840
|
+
toml: tomlFormat,
|
|
841
|
+
yaml: yamlFormat
|
|
842
|
+
};
|
|
843
|
+
var extensionMap = {
|
|
844
|
+
".json": "json",
|
|
845
|
+
".toml": "toml",
|
|
846
|
+
".yaml": "yaml",
|
|
847
|
+
".yml": "yaml"
|
|
848
|
+
};
|
|
849
|
+
function getConfigFormat(pathOrFormat) {
|
|
850
|
+
if (pathOrFormat in formatRegistry) {
|
|
851
|
+
return formatRegistry[pathOrFormat];
|
|
852
|
+
}
|
|
853
|
+
const ext = getExtension(pathOrFormat);
|
|
854
|
+
const formatName = extensionMap[ext];
|
|
855
|
+
if (!formatName) {
|
|
856
|
+
throw new Error(
|
|
857
|
+
`Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
return formatRegistry[formatName];
|
|
861
|
+
}
|
|
862
|
+
function detectFormat2(path25) {
|
|
863
|
+
const ext = getExtension(path25);
|
|
864
|
+
return extensionMap[ext];
|
|
865
|
+
}
|
|
866
|
+
function getExtension(path25) {
|
|
867
|
+
const lastDot = path25.lastIndexOf(".");
|
|
868
|
+
if (lastDot === -1) {
|
|
869
|
+
return "";
|
|
870
|
+
}
|
|
871
|
+
return path25.slice(lastDot).toLowerCase();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// packages/config-mutations/src/execution/path-utils.ts
|
|
875
|
+
import path5 from "node:path";
|
|
876
|
+
function expandHome(targetPath, homeDir) {
|
|
877
|
+
if (!targetPath?.startsWith("~")) {
|
|
878
|
+
return targetPath;
|
|
879
|
+
}
|
|
880
|
+
if (targetPath.startsWith("~./")) {
|
|
881
|
+
targetPath = `~/.${targetPath.slice(3)}`;
|
|
882
|
+
}
|
|
883
|
+
let remainder = targetPath.slice(1);
|
|
884
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
885
|
+
remainder = remainder.slice(1);
|
|
886
|
+
} else if (remainder.startsWith(".")) {
|
|
887
|
+
remainder = remainder.slice(1);
|
|
888
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
889
|
+
remainder = remainder.slice(1);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return remainder.length === 0 ? homeDir : path5.join(homeDir, remainder);
|
|
893
|
+
}
|
|
894
|
+
function validateHomePath(targetPath) {
|
|
895
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
896
|
+
throw new Error("Target path must be a non-empty string.");
|
|
897
|
+
}
|
|
898
|
+
if (!targetPath.startsWith("~")) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
`All target paths must be home-relative (start with ~). Received: "${targetPath}"`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function resolvePath(rawPath, homeDir, pathMapper) {
|
|
905
|
+
validateHomePath(rawPath);
|
|
906
|
+
const expanded = expandHome(rawPath, homeDir);
|
|
907
|
+
if (!pathMapper) {
|
|
908
|
+
return expanded;
|
|
909
|
+
}
|
|
910
|
+
const rawDirectory = path5.dirname(expanded);
|
|
911
|
+
const mappedDirectory = pathMapper.mapTargetDirectory({
|
|
912
|
+
targetDirectory: rawDirectory
|
|
913
|
+
});
|
|
914
|
+
const filename = path5.basename(expanded);
|
|
915
|
+
return filename.length === 0 ? mappedDirectory : path5.join(mappedDirectory, filename);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// packages/config-mutations/src/fs-utils.ts
|
|
919
|
+
function isNotFound(error) {
|
|
920
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
921
|
+
}
|
|
922
|
+
async function readFileIfExists(fs13, target) {
|
|
923
|
+
try {
|
|
924
|
+
return await fs13.readFile(target, "utf8");
|
|
925
|
+
} catch (error) {
|
|
926
|
+
if (isNotFound(error)) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async function pathExists(fs13, target) {
|
|
933
|
+
try {
|
|
934
|
+
await fs13.stat(target);
|
|
935
|
+
return true;
|
|
936
|
+
} catch (error) {
|
|
937
|
+
if (isNotFound(error)) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function createTimestamp() {
|
|
944
|
+
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// packages/config-mutations/src/execution/apply-mutation.ts
|
|
948
|
+
function resolveValue(resolver, options) {
|
|
949
|
+
if (typeof resolver === "function") {
|
|
950
|
+
return resolver(options);
|
|
951
|
+
}
|
|
952
|
+
return resolver;
|
|
953
|
+
}
|
|
954
|
+
function createInvalidDocumentBackupPath(targetPath) {
|
|
955
|
+
const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
|
|
956
|
+
return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
|
|
957
|
+
}
|
|
958
|
+
async function backupInvalidDocument(fs13, targetPath, content) {
|
|
959
|
+
const backupPath = createInvalidDocumentBackupPath(targetPath);
|
|
960
|
+
await fs13.writeFile(backupPath, content, { encoding: "utf8" });
|
|
961
|
+
}
|
|
962
|
+
function describeMutation(kind, targetPath) {
|
|
963
|
+
const displayPath = targetPath ?? "target";
|
|
964
|
+
switch (kind) {
|
|
965
|
+
case "ensureDirectory":
|
|
966
|
+
return `Create ${displayPath}`;
|
|
967
|
+
case "removeDirectory":
|
|
968
|
+
return `Remove directory ${displayPath}`;
|
|
969
|
+
case "backup":
|
|
970
|
+
return `Backup ${displayPath}`;
|
|
971
|
+
case "templateWrite":
|
|
972
|
+
return `Write ${displayPath}`;
|
|
973
|
+
case "chmod":
|
|
974
|
+
return `Set permissions on ${displayPath}`;
|
|
975
|
+
case "removeFile":
|
|
976
|
+
return `Remove ${displayPath}`;
|
|
977
|
+
case "configMerge":
|
|
978
|
+
case "configPrune":
|
|
979
|
+
case "configTransform":
|
|
980
|
+
case "templateMergeToml":
|
|
981
|
+
case "templateMergeJson":
|
|
982
|
+
return `Update ${displayPath}`;
|
|
983
|
+
default:
|
|
984
|
+
return "Operation";
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function pruneKeysByPrefix(table, prefix) {
|
|
988
|
+
const result = {};
|
|
989
|
+
for (const [key, value] of Object.entries(table)) {
|
|
990
|
+
if (!key.startsWith(prefix)) {
|
|
991
|
+
result[key] = value;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return result;
|
|
995
|
+
}
|
|
996
|
+
function isConfigObject4(value) {
|
|
997
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
998
|
+
}
|
|
999
|
+
function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
|
|
1000
|
+
const result = { ...base };
|
|
1001
|
+
const prefixMap = pruneByPrefix ?? {};
|
|
1002
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
1003
|
+
const current = result[key];
|
|
1004
|
+
const prefix = prefixMap[key];
|
|
1005
|
+
if (isConfigObject4(current) && isConfigObject4(value)) {
|
|
1006
|
+
if (prefix) {
|
|
1007
|
+
const pruned = pruneKeysByPrefix(current, prefix);
|
|
1008
|
+
result[key] = { ...pruned, ...value };
|
|
1009
|
+
} else {
|
|
1010
|
+
result[key] = mergeWithPruneByPrefix(
|
|
1011
|
+
current,
|
|
1012
|
+
value,
|
|
1013
|
+
prefixMap
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
result[key] = value;
|
|
1019
|
+
}
|
|
1020
|
+
return result;
|
|
1021
|
+
}
|
|
1022
|
+
async function applyMutation(mutation, context, options) {
|
|
1023
|
+
switch (mutation.kind) {
|
|
1024
|
+
case "ensureDirectory":
|
|
1025
|
+
return applyEnsureDirectory(mutation, context, options);
|
|
1026
|
+
case "removeDirectory":
|
|
1027
|
+
return applyRemoveDirectory(mutation, context, options);
|
|
1028
|
+
case "removeFile":
|
|
1029
|
+
return applyRemoveFile(mutation, context, options);
|
|
1030
|
+
case "chmod":
|
|
1031
|
+
return applyChmod(mutation, context, options);
|
|
1032
|
+
case "backup":
|
|
1033
|
+
return applyBackup(mutation, context, options);
|
|
1034
|
+
case "configMerge":
|
|
1035
|
+
return applyConfigMerge(mutation, context, options);
|
|
1036
|
+
case "configPrune":
|
|
1037
|
+
return applyConfigPrune(mutation, context, options);
|
|
1038
|
+
case "configTransform":
|
|
1039
|
+
return applyConfigTransform(mutation, context, options);
|
|
1040
|
+
case "templateWrite":
|
|
1041
|
+
return applyTemplateWrite(mutation, context, options);
|
|
1042
|
+
case "templateMergeToml":
|
|
1043
|
+
return applyTemplateMerge(mutation, context, options, "toml");
|
|
1044
|
+
case "templateMergeJson":
|
|
1045
|
+
return applyTemplateMerge(mutation, context, options, "json");
|
|
1046
|
+
default: {
|
|
1047
|
+
const never = mutation;
|
|
1048
|
+
throw new Error(`Unknown mutation kind: ${never.kind}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
async function applyEnsureDirectory(mutation, context, options) {
|
|
1053
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
1054
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1055
|
+
const details = {
|
|
1056
|
+
kind: mutation.kind,
|
|
1057
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1058
|
+
targetPath
|
|
1059
|
+
};
|
|
1060
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
1061
|
+
if (!context.dryRun) {
|
|
1062
|
+
await context.fs.mkdir(targetPath, { recursive: true });
|
|
1063
|
+
}
|
|
1064
|
+
return {
|
|
1065
|
+
outcome: {
|
|
1066
|
+
changed: !existed,
|
|
1067
|
+
effect: "mkdir",
|
|
1068
|
+
detail: existed ? "noop" : "create"
|
|
1069
|
+
},
|
|
1070
|
+
details
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
async function applyRemoveDirectory(mutation, context, options) {
|
|
1074
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
1075
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1076
|
+
const details = {
|
|
1077
|
+
kind: mutation.kind,
|
|
1078
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1079
|
+
targetPath
|
|
1080
|
+
};
|
|
1081
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
1082
|
+
if (!existed) {
|
|
1083
|
+
return {
|
|
1084
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1085
|
+
details
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof context.fs.rm !== "function") {
|
|
1089
|
+
return {
|
|
1090
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1091
|
+
details
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
if (mutation.force) {
|
|
1095
|
+
if (!context.dryRun) {
|
|
1096
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
1100
|
+
details
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
const entries = await context.fs.readdir(targetPath);
|
|
1104
|
+
if (entries.length > 0) {
|
|
1105
|
+
return {
|
|
1106
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1107
|
+
details
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
if (!context.dryRun) {
|
|
1111
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
1115
|
+
details
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
async function applyRemoveFile(mutation, context, options) {
|
|
1119
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1120
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1121
|
+
const details = {
|
|
1122
|
+
kind: mutation.kind,
|
|
1123
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1124
|
+
targetPath
|
|
1125
|
+
};
|
|
1126
|
+
try {
|
|
1127
|
+
const content = await context.fs.readFile(targetPath, "utf8");
|
|
1128
|
+
const trimmed = content.trim();
|
|
1129
|
+
if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
|
|
1130
|
+
return {
|
|
1131
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1132
|
+
details
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
if (mutation.whenEmpty && trimmed.length > 0) {
|
|
1136
|
+
return {
|
|
1137
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1138
|
+
details
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (!context.dryRun) {
|
|
1142
|
+
await context.fs.unlink(targetPath);
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
1146
|
+
details
|
|
1147
|
+
};
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
if (isNotFound(error)) {
|
|
1150
|
+
return {
|
|
1151
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1152
|
+
details
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
throw error;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
async function applyChmod(mutation, context, options) {
|
|
1159
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1160
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1161
|
+
const details = {
|
|
1162
|
+
kind: mutation.kind,
|
|
1163
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1164
|
+
targetPath
|
|
1165
|
+
};
|
|
1166
|
+
if (typeof context.fs.chmod !== "function") {
|
|
1167
|
+
return {
|
|
1168
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1169
|
+
details
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
const stat6 = await context.fs.stat(targetPath);
|
|
1174
|
+
const currentMode = typeof stat6.mode === "number" ? stat6.mode & 511 : null;
|
|
1175
|
+
if (currentMode === mutation.mode) {
|
|
1176
|
+
return {
|
|
1177
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1178
|
+
details
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
if (!context.dryRun) {
|
|
1182
|
+
await context.fs.chmod(targetPath, mutation.mode);
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
outcome: { changed: true, effect: "chmod", detail: "update" },
|
|
1186
|
+
details
|
|
1187
|
+
};
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
if (isNotFound(error)) {
|
|
1190
|
+
return {
|
|
1191
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1192
|
+
details
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
throw error;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
async function applyBackup(mutation, context, options) {
|
|
1199
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1200
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1201
|
+
const details = {
|
|
1202
|
+
kind: mutation.kind,
|
|
1203
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1204
|
+
targetPath
|
|
1205
|
+
};
|
|
1206
|
+
const content = await readFileIfExists(context.fs, targetPath);
|
|
1207
|
+
if (content === null) {
|
|
1208
|
+
return {
|
|
1209
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1210
|
+
details
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
if (!context.dryRun) {
|
|
1214
|
+
const backupPath = `${targetPath}.backup-${createTimestamp()}`;
|
|
1215
|
+
await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
outcome: { changed: true, effect: "copy", detail: "backup" },
|
|
1219
|
+
details
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
async function applyConfigMerge(mutation, context, options) {
|
|
1223
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1224
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1225
|
+
const details = {
|
|
1226
|
+
kind: mutation.kind,
|
|
1227
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1228
|
+
targetPath
|
|
1229
|
+
};
|
|
1230
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1231
|
+
if (!formatName) {
|
|
1232
|
+
throw new Error(
|
|
1233
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
const format = getConfigFormat(formatName);
|
|
1237
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
1238
|
+
let current;
|
|
1239
|
+
try {
|
|
1240
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
1241
|
+
} catch {
|
|
1242
|
+
if (rawContent !== null) {
|
|
1243
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
1244
|
+
}
|
|
1245
|
+
current = {};
|
|
1246
|
+
}
|
|
1247
|
+
const value = resolveValue(mutation.value, options);
|
|
1248
|
+
let merged;
|
|
1249
|
+
if (mutation.pruneByPrefix) {
|
|
1250
|
+
merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
|
|
1251
|
+
} else {
|
|
1252
|
+
merged = format.merge(current, value);
|
|
1253
|
+
}
|
|
1254
|
+
const serialized = format.serialize(merged);
|
|
1255
|
+
const changed = serialized !== rawContent;
|
|
1256
|
+
if (changed && !context.dryRun) {
|
|
1257
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
outcome: {
|
|
1261
|
+
changed,
|
|
1262
|
+
effect: changed ? "write" : "none",
|
|
1263
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
1264
|
+
},
|
|
1265
|
+
details
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
async function applyConfigPrune(mutation, context, options) {
|
|
1269
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1270
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1271
|
+
const details = {
|
|
1272
|
+
kind: mutation.kind,
|
|
1273
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1274
|
+
targetPath
|
|
1275
|
+
};
|
|
1276
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
1277
|
+
if (rawContent === null) {
|
|
1278
|
+
return {
|
|
1279
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1280
|
+
details
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1284
|
+
if (!formatName) {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
const format = getConfigFormat(formatName);
|
|
1290
|
+
let current;
|
|
1291
|
+
try {
|
|
1292
|
+
current = format.parse(rawContent);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return {
|
|
1295
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1296
|
+
details
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
|
|
1300
|
+
return {
|
|
1301
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1302
|
+
details
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
const shape = resolveValue(mutation.shape, options);
|
|
1306
|
+
const { changed, result } = format.prune(current, shape);
|
|
1307
|
+
if (!changed) {
|
|
1308
|
+
return {
|
|
1309
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1310
|
+
details
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
if (Object.keys(result).length === 0) {
|
|
1314
|
+
if (!context.dryRun) {
|
|
1315
|
+
await context.fs.unlink(targetPath);
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
1319
|
+
details
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const serialized = format.serialize(result);
|
|
1323
|
+
if (!context.dryRun) {
|
|
1324
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
outcome: { changed: true, effect: "write", detail: "update" },
|
|
1328
|
+
details
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
async function applyConfigTransform(mutation, context, options) {
|
|
1332
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1333
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1334
|
+
const details = {
|
|
1335
|
+
kind: mutation.kind,
|
|
1336
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1337
|
+
targetPath
|
|
1338
|
+
};
|
|
1339
|
+
const formatName = mutation.format ?? detectFormat2(rawPath);
|
|
1340
|
+
if (!formatName) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const format = getConfigFormat(formatName);
|
|
1346
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
1347
|
+
let current;
|
|
1348
|
+
try {
|
|
1349
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
1350
|
+
} catch {
|
|
1351
|
+
if (rawContent !== null) {
|
|
1352
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
1353
|
+
}
|
|
1354
|
+
current = {};
|
|
1355
|
+
}
|
|
1356
|
+
const { content: transformed, changed } = mutation.transform(current, options);
|
|
1357
|
+
if (!changed) {
|
|
1358
|
+
return {
|
|
1359
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1360
|
+
details
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (transformed === null) {
|
|
1364
|
+
if (rawContent === null) {
|
|
1365
|
+
return {
|
|
1366
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
1367
|
+
details
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
if (!context.dryRun) {
|
|
1371
|
+
await context.fs.unlink(targetPath);
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
1375
|
+
details
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const serialized = format.serialize(transformed);
|
|
1379
|
+
if (!context.dryRun) {
|
|
1380
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
1381
|
+
}
|
|
1382
|
+
return {
|
|
1383
|
+
outcome: {
|
|
1384
|
+
changed: true,
|
|
1385
|
+
effect: "write",
|
|
1386
|
+
detail: rawContent === null ? "create" : "update"
|
|
1387
|
+
},
|
|
1388
|
+
details
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
async function applyTemplateWrite(mutation, context, options) {
|
|
1392
|
+
if (!context.templates) {
|
|
1393
|
+
throw new Error(
|
|
1394
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1398
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1399
|
+
const details = {
|
|
1400
|
+
kind: mutation.kind,
|
|
1401
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1402
|
+
targetPath
|
|
1403
|
+
};
|
|
1404
|
+
const template = await context.templates(mutation.templateId);
|
|
1405
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
1406
|
+
const rendered = Mustache.render(template, templateContext);
|
|
1407
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
1408
|
+
if (!context.dryRun) {
|
|
1409
|
+
await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
|
|
1410
|
+
}
|
|
1411
|
+
return {
|
|
1412
|
+
outcome: {
|
|
1413
|
+
changed: true,
|
|
1414
|
+
effect: "write",
|
|
1415
|
+
detail: existed ? "update" : "create"
|
|
1416
|
+
},
|
|
1417
|
+
details
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
async function applyTemplateMerge(mutation, context, options, formatName) {
|
|
1421
|
+
if (!context.templates) {
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
1427
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
1428
|
+
const details = {
|
|
1429
|
+
kind: mutation.kind,
|
|
1430
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
1431
|
+
targetPath
|
|
1432
|
+
};
|
|
1433
|
+
const format = getConfigFormat(formatName);
|
|
1434
|
+
const template = await context.templates(mutation.templateId);
|
|
1435
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
1436
|
+
const rendered = Mustache.render(template, templateContext);
|
|
1437
|
+
let templateDoc;
|
|
1438
|
+
try {
|
|
1439
|
+
templateDoc = format.parse(rendered);
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
throw new Error(
|
|
1442
|
+
`Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`,
|
|
1443
|
+
{ cause: error }
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
1447
|
+
let current;
|
|
1448
|
+
try {
|
|
1449
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
1450
|
+
} catch {
|
|
1451
|
+
if (rawContent !== null) {
|
|
1452
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
1453
|
+
}
|
|
1454
|
+
current = {};
|
|
1455
|
+
}
|
|
1456
|
+
const merged = format.merge(current, templateDoc);
|
|
1457
|
+
const serialized = format.serialize(merged);
|
|
1458
|
+
const changed = serialized !== rawContent;
|
|
1459
|
+
if (changed && !context.dryRun) {
|
|
1460
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
outcome: {
|
|
1464
|
+
changed,
|
|
1465
|
+
effect: changed ? "write" : "none",
|
|
1466
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
1467
|
+
},
|
|
1468
|
+
details
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// packages/config-mutations/src/execution/run-mutations.ts
|
|
1473
|
+
async function runMutations(mutations, context, options) {
|
|
1474
|
+
const effects = [];
|
|
1475
|
+
let anyChanged = false;
|
|
1476
|
+
const resolverOptions = options ?? {};
|
|
1477
|
+
for (const mutation of mutations) {
|
|
1478
|
+
const { outcome } = await executeMutation(
|
|
1479
|
+
mutation,
|
|
1480
|
+
context,
|
|
1481
|
+
resolverOptions
|
|
1482
|
+
);
|
|
1483
|
+
effects.push(outcome);
|
|
1484
|
+
if (outcome.changed) {
|
|
1485
|
+
anyChanged = true;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return {
|
|
1489
|
+
changed: anyChanged,
|
|
1490
|
+
effects
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
async function executeMutation(mutation, context, options) {
|
|
1494
|
+
context.observers?.onStart?.({
|
|
1495
|
+
kind: mutation.kind,
|
|
1496
|
+
label: mutation.label ?? mutation.kind,
|
|
1497
|
+
targetPath: void 0
|
|
1498
|
+
// Will be resolved during apply
|
|
1499
|
+
});
|
|
1500
|
+
try {
|
|
1501
|
+
const { outcome, details } = await applyMutation(mutation, context, options);
|
|
1502
|
+
context.observers?.onComplete?.(details, outcome);
|
|
1503
|
+
return { outcome, details };
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
context.observers?.onError?.(
|
|
1506
|
+
{
|
|
1507
|
+
kind: mutation.kind,
|
|
1508
|
+
label: mutation.label ?? mutation.kind,
|
|
1509
|
+
targetPath: void 0
|
|
1510
|
+
},
|
|
1511
|
+
error
|
|
1512
|
+
);
|
|
1513
|
+
throw error;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// packages/config-mutations/src/template/render.ts
|
|
1518
|
+
import Mustache2 from "mustache";
|
|
1519
|
+
var originalEscape = Mustache2.escape;
|
|
1520
|
+
|
|
1521
|
+
// packages/poe-code-config/src/store.ts
|
|
1522
|
+
async function readMergedDocument(fs13, globalPath, projectPath) {
|
|
1523
|
+
const globalDocument = await readStoredDocument(fs13, globalPath);
|
|
1524
|
+
if (!projectPath) {
|
|
1525
|
+
return globalDocument.data;
|
|
1526
|
+
}
|
|
1527
|
+
const projectDocument = await readStoredDocument(fs13, projectPath);
|
|
1528
|
+
const resolved = await resolve(
|
|
1529
|
+
[
|
|
1530
|
+
{
|
|
1531
|
+
source: "project",
|
|
1532
|
+
filePath: projectPath,
|
|
1533
|
+
content: projectDocument.content
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
source: "base",
|
|
1537
|
+
path: path6.dirname(globalPath)
|
|
1538
|
+
}
|
|
1539
|
+
],
|
|
1540
|
+
{
|
|
1541
|
+
fs: createResolvedConfigFs(fs13, globalPath, globalDocument.content),
|
|
1542
|
+
autoExtend: true
|
|
1543
|
+
}
|
|
1544
|
+
);
|
|
1545
|
+
return normalizeDocument(resolved.data);
|
|
1546
|
+
}
|
|
1547
|
+
async function readStoredDocument(fs13, filePath) {
|
|
1548
|
+
try {
|
|
1549
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
1550
|
+
return await parseStoredDocument(fs13, filePath, raw);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
if (isNotFound(error)) {
|
|
1553
|
+
return {
|
|
1554
|
+
content: EMPTY_DOCUMENT,
|
|
1555
|
+
data: {}
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
throw error;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
async function parseStoredDocument(fs13, filePath, raw) {
|
|
1562
|
+
try {
|
|
1563
|
+
return {
|
|
1564
|
+
content: raw,
|
|
1565
|
+
data: normalizeDocument(JSON.parse(raw))
|
|
1566
|
+
};
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
if (error instanceof SyntaxError) {
|
|
1569
|
+
await recoverInvalidDocument(fs13, filePath, raw);
|
|
1570
|
+
return {
|
|
1571
|
+
content: EMPTY_DOCUMENT,
|
|
1572
|
+
data: {}
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
throw error;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
function normalizeDocument(value) {
|
|
1579
|
+
if (!isRecord(value)) {
|
|
1580
|
+
return {};
|
|
1581
|
+
}
|
|
1582
|
+
const document = {};
|
|
1583
|
+
for (const [scope, scopeValues] of Object.entries(value)) {
|
|
1584
|
+
const normalizedValues = normalizeScopeValues(scopeValues);
|
|
1585
|
+
if (Object.keys(normalizedValues).length > 0) {
|
|
1586
|
+
document[scope] = normalizedValues;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return document;
|
|
1590
|
+
}
|
|
1591
|
+
function normalizeScopeValues(value) {
|
|
1592
|
+
if (!isRecord(value)) {
|
|
1593
|
+
return {};
|
|
1594
|
+
}
|
|
1595
|
+
const normalized = {};
|
|
1596
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1597
|
+
if (entry !== void 0) {
|
|
1598
|
+
normalized[key] = entry;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return normalized;
|
|
1602
|
+
}
|
|
1603
|
+
function createResolvedConfigFs(fs13, globalPath, globalContent) {
|
|
1604
|
+
return {
|
|
1605
|
+
readFile(filePath, _encoding) {
|
|
1606
|
+
if (filePath === globalPath) {
|
|
1607
|
+
return Promise.resolve(globalContent);
|
|
1608
|
+
}
|
|
1609
|
+
return fs13.readFile(filePath, "utf8");
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
async function recoverInvalidDocument(fs13, filePath, content) {
|
|
1614
|
+
await fs13.mkdir(path6.dirname(filePath), { recursive: true });
|
|
1615
|
+
const backupPath = createInvalidBackupPath(filePath);
|
|
1616
|
+
await fs13.writeFile(backupPath, content, { encoding: "utf8" });
|
|
1617
|
+
await fs13.writeFile(filePath, EMPTY_DOCUMENT, { encoding: "utf8" });
|
|
1618
|
+
}
|
|
1619
|
+
function createInvalidBackupPath(filePath) {
|
|
1620
|
+
const directory = path6.dirname(filePath);
|
|
1621
|
+
const baseName = path6.basename(filePath);
|
|
1622
|
+
return path6.join(directory, `${baseName}.invalid-${createTimestamp()}.json`);
|
|
1623
|
+
}
|
|
1624
|
+
function isRecord(value) {
|
|
1625
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1626
|
+
}
|
|
1627
|
+
var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
|
|
1628
|
+
`;
|
|
1629
|
+
|
|
1630
|
+
// packages/poe-code-config/src/memory.ts
|
|
1631
|
+
async function configuredMemoryRoot(options) {
|
|
1632
|
+
return (await resolveMemoryConfig(options)).root;
|
|
1633
|
+
}
|
|
1634
|
+
async function resolveAgent(options, fallbackAgent) {
|
|
1635
|
+
const memory = await resolveMemoryConfig(options);
|
|
1636
|
+
return memory.ingestAgent ?? fallbackAgent;
|
|
1637
|
+
}
|
|
1638
|
+
async function configuredTimeout(options) {
|
|
1639
|
+
return (await resolveMemoryConfig(options)).ingestTimeoutMs;
|
|
1640
|
+
}
|
|
1641
|
+
async function cacheEnabled(options) {
|
|
1642
|
+
return (await resolveMemoryConfig(options)).cacheEnabled;
|
|
1643
|
+
}
|
|
1644
|
+
async function resolveMemoryConfig(options) {
|
|
1645
|
+
const document = await readMergedDocument(options.fs, options.filePath, options.projectFilePath);
|
|
1646
|
+
const memory = asRecord(document.memory);
|
|
1647
|
+
const cache = asRecord(memory?.cache);
|
|
1648
|
+
const mcp = asRecord(memory?.mcp);
|
|
1649
|
+
const query = asRecord(memory?.query);
|
|
1650
|
+
return {
|
|
1651
|
+
root: readString(memory?.root),
|
|
1652
|
+
ingestAgent: readString(memory?.ingestAgent),
|
|
1653
|
+
ingestTimeoutMs: readNumber(memory?.ingestTimeoutMs) ?? 3e5,
|
|
1654
|
+
cacheEnabled: readBoolean(cache?.enabled) ?? true,
|
|
1655
|
+
mcpWritesAllowed: readBoolean(mcp?.allowWrites) ?? false,
|
|
1656
|
+
defaultQueryBudget: readNumber(query?.defaultBudgetTokens) ?? 4096
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function asRecord(value) {
|
|
1660
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1661
|
+
return void 0;
|
|
1662
|
+
}
|
|
1663
|
+
return value;
|
|
1664
|
+
}
|
|
1665
|
+
function readString(value) {
|
|
1666
|
+
return typeof value === "string" ? value : void 0;
|
|
1667
|
+
}
|
|
1668
|
+
function readNumber(value) {
|
|
1669
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1670
|
+
}
|
|
1671
|
+
function readBoolean(value) {
|
|
1672
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// packages/poe-code-config/src/inspect.ts
|
|
1676
|
+
import path7 from "node:path";
|
|
1677
|
+
var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
|
|
1678
|
+
`;
|
|
1679
|
+
|
|
1680
|
+
// packages/memory/src/resolve-root.ts
|
|
1681
|
+
var MEMORY_ROOT_ENV_VAR = "POE_CODE_MEMORY_ROOT";
|
|
1682
|
+
async function resolveConfiguredMemoryRoot(options) {
|
|
1683
|
+
const envOverride = options.env[MEMORY_ROOT_ENV_VAR]?.trim();
|
|
1684
|
+
if (envOverride && envOverride.length > 0) {
|
|
1685
|
+
return resolveAgainstCwd(options.cwd, envOverride);
|
|
1686
|
+
}
|
|
1687
|
+
const configOverride = (await configuredMemoryRoot({
|
|
1688
|
+
fs: options.fs,
|
|
1689
|
+
filePath: options.configPath,
|
|
1690
|
+
projectFilePath: options.projectConfigPath
|
|
1691
|
+
}))?.trim();
|
|
1692
|
+
if (configOverride && configOverride.length > 0) {
|
|
1693
|
+
return resolveAgainstCwd(options.cwd, configOverride);
|
|
1694
|
+
}
|
|
1695
|
+
return resolveMemoryRoot(options.cwd);
|
|
1696
|
+
}
|
|
1697
|
+
function resolveAgainstCwd(cwd, value) {
|
|
1698
|
+
return path8.isAbsolute(value) ? value : path8.resolve(cwd, value);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// packages/memory/src/init.ts
|
|
1702
|
+
import * as fs from "node:fs/promises";
|
|
1703
|
+
import path9 from "node:path";
|
|
1704
|
+
async function initMemory(root) {
|
|
1705
|
+
await fs.mkdir(path9.join(root, MEMORY_PAGES_DIR_RELPATH), { recursive: true });
|
|
1706
|
+
await writeFileIfMissing(path9.join(root, MEMORY_INDEX_RELPATH), "# Memory index\n");
|
|
1707
|
+
await writeFileIfMissing(path9.join(root, MEMORY_LOG_RELPATH), "");
|
|
1708
|
+
}
|
|
1709
|
+
async function writeFileIfMissing(filePath, content) {
|
|
1710
|
+
try {
|
|
1711
|
+
await fs.writeFile(filePath, content, { encoding: "utf8", flag: "wx" });
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
if (!hasErrorCode(error, "EEXIST")) {
|
|
1714
|
+
throw error;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
function hasErrorCode(error, code) {
|
|
1719
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// packages/memory/src/pages.ts
|
|
1723
|
+
import * as fs2 from "node:fs/promises";
|
|
1724
|
+
import path10 from "node:path";
|
|
1725
|
+
|
|
1726
|
+
// packages/memory/src/frontmatter.ts
|
|
1727
|
+
import { parse as parse5, stringify } from "yaml";
|
|
1728
|
+
function parseFrontmatter(markdown) {
|
|
1729
|
+
const content = markdown.startsWith("\uFEFF") ? markdown.slice(1) : markdown;
|
|
1730
|
+
const openingLineBreak = readOpeningLineBreak(content);
|
|
1731
|
+
if (openingLineBreak === void 0) {
|
|
1732
|
+
return {
|
|
1733
|
+
frontmatter: {},
|
|
1734
|
+
body: markdown
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
const frontmatterStart = 3 + openingLineBreak.length;
|
|
1738
|
+
const closingFenceIndex = findClosingFence(content, frontmatterStart);
|
|
1739
|
+
const yamlBlock = content.slice(frontmatterStart, closingFenceIndex);
|
|
1740
|
+
const bodyStart = closingFenceIndex + 4;
|
|
1741
|
+
return {
|
|
1742
|
+
frontmatter: parsePageFrontmatter(parseYamlFrontmatter(yamlBlock)),
|
|
1743
|
+
body: readBody(content, bodyStart)
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function serializeFrontmatter(frontmatter, body) {
|
|
1747
|
+
const serialized = {
|
|
1748
|
+
...frontmatter.name === void 0 ? {} : { name: frontmatter.name },
|
|
1749
|
+
...frontmatter.description === void 0 ? {} : { description: frontmatter.description },
|
|
1750
|
+
...frontmatter.lastTouchedAt === void 0 ? {} : { last_touched_at: frontmatter.lastTouchedAt },
|
|
1751
|
+
...frontmatter.sources === void 0 || frontmatter.sources.length === 0 ? {} : { sources: frontmatter.sources.map((source) => serializeSourceRef(source)) }
|
|
1752
|
+
};
|
|
1753
|
+
if (Object.keys(serialized).length === 0) {
|
|
1754
|
+
return body;
|
|
1755
|
+
}
|
|
1756
|
+
return `---
|
|
1757
|
+
${stringify(serialized).trimEnd()}
|
|
1758
|
+
---
|
|
1759
|
+
${body}`;
|
|
1760
|
+
}
|
|
1761
|
+
function parseSourceRef(serialized) {
|
|
1762
|
+
const [rawPath, rawAnchor] = serialized.split("#", 2);
|
|
1763
|
+
const normalizedPath = rawPath?.trim();
|
|
1764
|
+
if (normalizedPath === void 0 || normalizedPath.length === 0) {
|
|
1765
|
+
throw new Error(`Invalid source ref "${serialized}".`);
|
|
1766
|
+
}
|
|
1767
|
+
if (rawAnchor === void 0) {
|
|
1768
|
+
return { path: normalizedPath };
|
|
1769
|
+
}
|
|
1770
|
+
const singleLineMatch = /^L(\d+)$/.exec(rawAnchor);
|
|
1771
|
+
if (singleLineMatch !== null) {
|
|
1772
|
+
const startLine = Number.parseInt(singleLineMatch[1], 10);
|
|
1773
|
+
assertValidLineNumber(startLine, serialized);
|
|
1774
|
+
return {
|
|
1775
|
+
path: normalizedPath,
|
|
1776
|
+
startLine
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
const rangeMatch = /^L(\d+)-L?(\d+)$/.exec(rawAnchor);
|
|
1780
|
+
if (rangeMatch !== null) {
|
|
1781
|
+
const startLine = Number.parseInt(rangeMatch[1], 10);
|
|
1782
|
+
const endLine = Number.parseInt(rangeMatch[2], 10);
|
|
1783
|
+
assertValidLineNumber(startLine, serialized);
|
|
1784
|
+
assertValidLineNumber(endLine, serialized);
|
|
1785
|
+
if (endLine < startLine) {
|
|
1786
|
+
throw new Error(`Invalid source ref "${serialized}": line range is reversed.`);
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
path: normalizedPath,
|
|
1790
|
+
startLine,
|
|
1791
|
+
endLine
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
throw new Error(`Invalid source ref "${serialized}".`);
|
|
1795
|
+
}
|
|
1796
|
+
function serializeSourceRef(source) {
|
|
1797
|
+
if (source.path.trim().length === 0) {
|
|
1798
|
+
throw new Error("Source path cannot be empty.");
|
|
1799
|
+
}
|
|
1800
|
+
if (source.startLine === void 0) {
|
|
1801
|
+
if (source.endLine !== void 0) {
|
|
1802
|
+
throw new Error("Source endLine requires startLine.");
|
|
1803
|
+
}
|
|
1804
|
+
return source.path;
|
|
1805
|
+
}
|
|
1806
|
+
assertValidLineNumber(source.startLine, source.path);
|
|
1807
|
+
if (source.endLine === void 0) {
|
|
1808
|
+
return `${source.path}#L${source.startLine}`;
|
|
1809
|
+
}
|
|
1810
|
+
assertValidLineNumber(source.endLine, source.path);
|
|
1811
|
+
if (source.endLine < source.startLine) {
|
|
1812
|
+
throw new Error(`Invalid source ref "${source.path}": line range is reversed.`);
|
|
1813
|
+
}
|
|
1814
|
+
return `${source.path}#L${source.startLine}-L${source.endLine}`;
|
|
1815
|
+
}
|
|
1816
|
+
function readOpeningLineBreak(markdown) {
|
|
1817
|
+
if (!markdown.startsWith("---")) {
|
|
1818
|
+
return void 0;
|
|
1819
|
+
}
|
|
1820
|
+
const nextCharacter = markdown[3];
|
|
1821
|
+
if (nextCharacter === "\n") {
|
|
1822
|
+
return "\n";
|
|
1823
|
+
}
|
|
1824
|
+
if (nextCharacter === "\r" && markdown[4] === "\n") {
|
|
1825
|
+
return "\r\n";
|
|
1826
|
+
}
|
|
1827
|
+
return nextCharacter === void 0 ? "\n" : void 0;
|
|
1828
|
+
}
|
|
1829
|
+
function findClosingFence(markdown, searchFrom) {
|
|
1830
|
+
let currentIndex = searchFrom - 1;
|
|
1831
|
+
while (currentIndex < markdown.length) {
|
|
1832
|
+
const candidateIndex = markdown.indexOf("\n---", currentIndex);
|
|
1833
|
+
if (candidateIndex === -1) {
|
|
1834
|
+
throw new Error("Missing YAML frontmatter end delimiter (---).");
|
|
1835
|
+
}
|
|
1836
|
+
const fenceEnd = candidateIndex + 4;
|
|
1837
|
+
const nextCharacter = markdown[fenceEnd];
|
|
1838
|
+
if (nextCharacter === "\n" || nextCharacter === void 0) {
|
|
1839
|
+
return candidateIndex;
|
|
1840
|
+
}
|
|
1841
|
+
if (nextCharacter === "\r" && markdown[fenceEnd + 1] === "\n") {
|
|
1842
|
+
return candidateIndex;
|
|
1843
|
+
}
|
|
1844
|
+
currentIndex = fenceEnd;
|
|
1845
|
+
}
|
|
1846
|
+
throw new Error("Missing YAML frontmatter end delimiter (---).");
|
|
1847
|
+
}
|
|
1848
|
+
function readBody(markdown, bodyStart) {
|
|
1849
|
+
const nextCharacter = markdown[bodyStart];
|
|
1850
|
+
if (nextCharacter === "\n") {
|
|
1851
|
+
return markdown.slice(bodyStart + 1);
|
|
1852
|
+
}
|
|
1853
|
+
if (nextCharacter === "\r" && markdown[bodyStart + 1] === "\n") {
|
|
1854
|
+
return markdown.slice(bodyStart + 2);
|
|
1855
|
+
}
|
|
1856
|
+
return markdown.slice(bodyStart);
|
|
1857
|
+
}
|
|
1858
|
+
function parseYamlFrontmatter(yamlBlock) {
|
|
1859
|
+
const normalizedYamlBlock = yamlBlock.includes("\r") ? yamlBlock.replaceAll("\r\n", "\n").replaceAll("\r", "") : yamlBlock;
|
|
1860
|
+
let parsed;
|
|
1861
|
+
try {
|
|
1862
|
+
parsed = parse5(normalizedYamlBlock);
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
const message = error instanceof Error ? error.message : "Unknown YAML parse error";
|
|
1865
|
+
throw new Error(`Invalid YAML frontmatter: ${message}`);
|
|
1866
|
+
}
|
|
1867
|
+
if (parsed === null) {
|
|
1868
|
+
return {};
|
|
1869
|
+
}
|
|
1870
|
+
if (!isRecord2(parsed)) {
|
|
1871
|
+
throw new Error("YAML frontmatter must parse to an object.");
|
|
1872
|
+
}
|
|
1873
|
+
return parsed;
|
|
1874
|
+
}
|
|
1875
|
+
function parsePageFrontmatter(value) {
|
|
1876
|
+
const name = readOptionalString(value.name, "name");
|
|
1877
|
+
const description = readOptionalString(value.description, "description");
|
|
1878
|
+
const lastTouchedAt = readOptionalString(
|
|
1879
|
+
value.last_touched_at ?? value.lastTouchedAt,
|
|
1880
|
+
"last_touched_at"
|
|
1881
|
+
);
|
|
1882
|
+
const sources = parseSources(value.sources);
|
|
1883
|
+
return {
|
|
1884
|
+
...name === void 0 ? {} : { name },
|
|
1885
|
+
...description === void 0 ? {} : { description },
|
|
1886
|
+
...lastTouchedAt === void 0 ? {} : { lastTouchedAt },
|
|
1887
|
+
...sources === void 0 ? {} : { sources }
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function parseSources(value) {
|
|
1891
|
+
if (value === void 0) {
|
|
1892
|
+
return void 0;
|
|
1893
|
+
}
|
|
1894
|
+
if (!Array.isArray(value)) {
|
|
1895
|
+
throw new Error('Invalid "sources" frontmatter. Expected an array.');
|
|
1896
|
+
}
|
|
1897
|
+
return value.map((item) => {
|
|
1898
|
+
if (typeof item === "string") {
|
|
1899
|
+
return parseSourceRef(item);
|
|
1900
|
+
}
|
|
1901
|
+
if (!isRecord2(item)) {
|
|
1902
|
+
throw new Error('Invalid "sources" frontmatter. Expected each source to be a string or object.');
|
|
1903
|
+
}
|
|
1904
|
+
const path25 = readRequiredString(item.path, "sources[].path");
|
|
1905
|
+
const startLine = readOptionalPositiveInteger(item.startLine, "sources[].startLine");
|
|
1906
|
+
const endLine = readOptionalPositiveInteger(item.endLine, "sources[].endLine");
|
|
1907
|
+
return parseSourceRef(serializeSourceRef({ path: path25, ...startLine === void 0 ? {} : { startLine }, ...endLine === void 0 ? {} : { endLine } }));
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
function readOptionalString(value, field) {
|
|
1911
|
+
if (value === void 0 || value === null) {
|
|
1912
|
+
return void 0;
|
|
1913
|
+
}
|
|
1914
|
+
if (typeof value !== "string") {
|
|
1915
|
+
throw new Error(`Invalid "${field}" frontmatter. Expected a string.`);
|
|
1916
|
+
}
|
|
1917
|
+
return value;
|
|
1918
|
+
}
|
|
1919
|
+
function readRequiredString(value, field) {
|
|
1920
|
+
const parsed = readOptionalString(value, field);
|
|
1921
|
+
if (parsed === void 0) {
|
|
1922
|
+
throw new Error(`Invalid "${field}" frontmatter. Expected a string.`);
|
|
1923
|
+
}
|
|
1924
|
+
return parsed;
|
|
1925
|
+
}
|
|
1926
|
+
function readOptionalPositiveInteger(value, field) {
|
|
1927
|
+
if (value === void 0 || value === null) {
|
|
1928
|
+
return void 0;
|
|
1929
|
+
}
|
|
1930
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1931
|
+
throw new Error(`Invalid "${field}" frontmatter. Expected a positive integer.`);
|
|
1932
|
+
}
|
|
1933
|
+
return value;
|
|
1934
|
+
}
|
|
1935
|
+
function assertValidLineNumber(line, value) {
|
|
1936
|
+
if (!Number.isInteger(line) || line <= 0) {
|
|
1937
|
+
throw new Error(`Invalid source ref "${value}": line numbers must be positive integers.`);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
function isRecord2(value) {
|
|
1941
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// packages/memory/src/pages.ts
|
|
1945
|
+
async function listPages(root) {
|
|
1946
|
+
const relPaths = await collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH);
|
|
1947
|
+
const pages = await Promise.all(relPaths.map(async (relPath) => readPage(root, relPath)));
|
|
1948
|
+
return pages.sort((left, right) => left.relPath.localeCompare(right.relPath));
|
|
1949
|
+
}
|
|
1950
|
+
async function readPage(root, relPath) {
|
|
1951
|
+
const normalizedRelPath = assertMarkdownRelPath(relPath);
|
|
1952
|
+
const absPath = path10.join(root, normalizedRelPath);
|
|
1953
|
+
const [content, stat6] = await Promise.all([fs2.readFile(absPath, "utf8"), fs2.stat(absPath)]);
|
|
1954
|
+
try {
|
|
1955
|
+
const parsed = parseFrontmatter(content);
|
|
1956
|
+
return {
|
|
1957
|
+
relPath: normalizedRelPath,
|
|
1958
|
+
frontmatter: parsed.frontmatter,
|
|
1959
|
+
body: parsed.body,
|
|
1960
|
+
bytes: Buffer.byteLength(content),
|
|
1961
|
+
mtimeMs: stat6.mtimeMs
|
|
1962
|
+
};
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1965
|
+
console.warn(`Failed to parse frontmatter for "${normalizedRelPath}": ${message}`);
|
|
1966
|
+
return {
|
|
1967
|
+
relPath: normalizedRelPath,
|
|
1968
|
+
frontmatter: {},
|
|
1969
|
+
body: content,
|
|
1970
|
+
bytes: Buffer.byteLength(content),
|
|
1971
|
+
mtimeMs: stat6.mtimeMs
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
async function collectMarkdownRelPaths(root, startRelPath = "") {
|
|
1976
|
+
const relPaths = [];
|
|
1977
|
+
await collectMarkdownRelPathsInto(root, startRelPath, relPaths);
|
|
1978
|
+
return relPaths.sort((left, right) => left.localeCompare(right));
|
|
1979
|
+
}
|
|
1980
|
+
async function collectMarkdownRelPathsInto(root, currentRelPath, relPaths) {
|
|
1981
|
+
const absPath = path10.join(root, currentRelPath);
|
|
1982
|
+
let entryNames;
|
|
1983
|
+
try {
|
|
1984
|
+
entryNames = await fs2.readdir(absPath);
|
|
1985
|
+
} catch (error) {
|
|
1986
|
+
if (isMissing(error)) {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
throw error;
|
|
1990
|
+
}
|
|
1991
|
+
for (const entryName of entryNames.sort((left, right) => left.localeCompare(right))) {
|
|
1992
|
+
const entryRelPath = currentRelPath.length === 0 ? entryName : path10.posix.join(currentRelPath, entryName);
|
|
1993
|
+
const entryAbsPath = path10.join(root, entryRelPath);
|
|
1994
|
+
const entryStat = await fs2.stat(entryAbsPath);
|
|
1995
|
+
if (entryStat.isDirectory()) {
|
|
1996
|
+
if (entryName === MEMORY_CACHE_DIR_RELPATH) {
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
await collectMarkdownRelPathsInto(root, entryRelPath, relPaths);
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
if (!entryStat.isFile()) {
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
if (!isMarkdownPath(entryRelPath)) {
|
|
2006
|
+
if (entryName === MEMORY_LOCK_RELPATH) {
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
console.warn(`Skipping non-markdown memory file "${entryRelPath}".`);
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
relPaths.push(entryRelPath);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function assertMarkdownRelPath(relPath) {
|
|
2016
|
+
const normalizedRelPath = assertSafeRelPath(relPath);
|
|
2017
|
+
if (!isMarkdownPath(normalizedRelPath)) {
|
|
2018
|
+
throw new Error(`Expected a markdown path, received "${relPath}".`);
|
|
2019
|
+
}
|
|
2020
|
+
return normalizedRelPath;
|
|
2021
|
+
}
|
|
2022
|
+
function isMarkdownPath(relPath) {
|
|
2023
|
+
return path10.posix.extname(relPath).toLowerCase() === ".md";
|
|
2024
|
+
}
|
|
2025
|
+
function isMissing(error) {
|
|
2026
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// packages/memory/src/search.ts
|
|
2030
|
+
import * as fs3 from "node:fs/promises";
|
|
2031
|
+
import path11 from "node:path";
|
|
2032
|
+
async function searchMemory(root, query) {
|
|
2033
|
+
const normalizedQuery = query.trim();
|
|
2034
|
+
if (normalizedQuery.length === 0) {
|
|
2035
|
+
throw new Error("Search query cannot be empty.");
|
|
2036
|
+
}
|
|
2037
|
+
const relPaths = await collectMarkdownRelPaths(root);
|
|
2038
|
+
const hits = [];
|
|
2039
|
+
for (const relPath of relPaths) {
|
|
2040
|
+
const content = await fs3.readFile(path11.join(root, relPath), "utf8");
|
|
2041
|
+
if (content.length === 0) {
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
const lines = content.replaceAll("\r\n", "\n").split("\n");
|
|
2045
|
+
for (const [index, line] of lines.entries()) {
|
|
2046
|
+
if (!line.includes(normalizedQuery)) {
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
hits.push({
|
|
2050
|
+
relPath,
|
|
2051
|
+
lineNumber: index + 1,
|
|
2052
|
+
line
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
return hits;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// packages/memory/src/status.ts
|
|
2060
|
+
import * as fs4 from "node:fs/promises";
|
|
2061
|
+
import path12 from "node:path";
|
|
2062
|
+
async function statusOf(root) {
|
|
2063
|
+
if (!await pathExists2(root)) {
|
|
2064
|
+
return {
|
|
2065
|
+
pageCount: 0,
|
|
2066
|
+
totalBytes: 0,
|
|
2067
|
+
lastWriteAt: null,
|
|
2068
|
+
initialized: false
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
const [pageRelPaths, markdownRelPaths] = await Promise.all([
|
|
2072
|
+
collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH),
|
|
2073
|
+
collectMarkdownRelPaths(root)
|
|
2074
|
+
]);
|
|
2075
|
+
let totalBytes = 0;
|
|
2076
|
+
let lastWriteAtMs = Number.NEGATIVE_INFINITY;
|
|
2077
|
+
for (const relPath of markdownRelPaths) {
|
|
2078
|
+
const stat6 = await fs4.stat(path12.join(root, relPath));
|
|
2079
|
+
totalBytes += stat6.size;
|
|
2080
|
+
lastWriteAtMs = Math.max(lastWriteAtMs, stat6.mtimeMs);
|
|
2081
|
+
}
|
|
2082
|
+
return {
|
|
2083
|
+
pageCount: pageRelPaths.length,
|
|
2084
|
+
totalBytes,
|
|
2085
|
+
lastWriteAt: Number.isFinite(lastWriteAtMs) ? new Date(lastWriteAtMs).toISOString() : null,
|
|
2086
|
+
initialized: true
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
async function pathExists2(targetPath) {
|
|
2090
|
+
try {
|
|
2091
|
+
await fs4.stat(targetPath);
|
|
2092
|
+
return true;
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
2095
|
+
return false;
|
|
2096
|
+
}
|
|
2097
|
+
throw error;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// packages/memory/src/edit.ts
|
|
2102
|
+
import * as fs7 from "node:fs/promises";
|
|
2103
|
+
import path16 from "node:path";
|
|
2104
|
+
|
|
2105
|
+
// packages/memory/src/write.ts
|
|
2106
|
+
import * as fs6 from "node:fs/promises";
|
|
2107
|
+
import path15 from "node:path";
|
|
2108
|
+
|
|
2109
|
+
// packages/memory/src/lock.ts
|
|
2110
|
+
import * as fsPromises from "node:fs/promises";
|
|
2111
|
+
import path13 from "node:path";
|
|
2112
|
+
function createDefaultFs() {
|
|
2113
|
+
return {
|
|
2114
|
+
readFile: (filePath, encoding) => fsPromises.readFile(filePath, encoding),
|
|
2115
|
+
unlink: fsPromises.unlink,
|
|
2116
|
+
writeFile: (filePath, data, options) => fsPromises.writeFile(filePath, data, options)
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
function sleep(ms) {
|
|
2120
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2121
|
+
}
|
|
2122
|
+
function hasErrorCode2(error, code) {
|
|
2123
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
2124
|
+
}
|
|
2125
|
+
function lockDelay(attempt, minTimeoutMs, maxTimeoutMs) {
|
|
2126
|
+
return Math.min(maxTimeoutMs, minTimeoutMs * Math.pow(2, attempt));
|
|
2127
|
+
}
|
|
2128
|
+
function parsePid(input) {
|
|
2129
|
+
const value = input.trim();
|
|
2130
|
+
if (value.length === 0) {
|
|
2131
|
+
return void 0;
|
|
2132
|
+
}
|
|
2133
|
+
for (const char of value) {
|
|
2134
|
+
if (char < "0" || char > "9") {
|
|
2135
|
+
return void 0;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
const pid = Number.parseInt(value, 10);
|
|
2139
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
2140
|
+
}
|
|
2141
|
+
function isPidRunning(pid) {
|
|
2142
|
+
try {
|
|
2143
|
+
process.kill(pid, 0);
|
|
2144
|
+
return true;
|
|
2145
|
+
} catch (error) {
|
|
2146
|
+
return !hasErrorCode2(error, "ESRCH");
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
async function removeLockFile(fs13, lockPath) {
|
|
2150
|
+
try {
|
|
2151
|
+
await fs13.unlink(lockPath);
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
if (!hasErrorCode2(error, "ENOENT")) {
|
|
2154
|
+
throw error;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
async function readLockPid(fs13, lockPath) {
|
|
2159
|
+
try {
|
|
2160
|
+
return parsePid(await fs13.readFile(lockPath, "utf8"));
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
if (hasErrorCode2(error, "ENOENT")) {
|
|
2163
|
+
return null;
|
|
2164
|
+
}
|
|
2165
|
+
throw error;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
async function withLock(root, run, options = {}) {
|
|
2169
|
+
const fs13 = options.fs ?? createDefaultFs();
|
|
2170
|
+
const lockPath = path13.join(root, MEMORY_LOCK_RELPATH);
|
|
2171
|
+
const pid = options.pid ?? process.pid;
|
|
2172
|
+
const retries = options.retries ?? 20;
|
|
2173
|
+
const minTimeoutMs = options.minTimeoutMs ?? 25;
|
|
2174
|
+
const maxTimeoutMs = options.maxTimeoutMs ?? 250;
|
|
2175
|
+
const pidIsRunning = options.isPidRunning ?? isPidRunning;
|
|
2176
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
2177
|
+
try {
|
|
2178
|
+
await fs13.writeFile(lockPath, `${pid}
|
|
2179
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
2180
|
+
try {
|
|
2181
|
+
return await run();
|
|
2182
|
+
} finally {
|
|
2183
|
+
await removeLockFile(fs13, lockPath);
|
|
2184
|
+
}
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
if (!hasErrorCode2(error, "EEXIST")) {
|
|
2187
|
+
throw error;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
const existingPid = await readLockPid(fs13, lockPath);
|
|
2191
|
+
if (existingPid === null) {
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
if (existingPid === void 0 || !pidIsRunning(existingPid)) {
|
|
2195
|
+
await removeLockFile(fs13, lockPath);
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (attempt < retries) {
|
|
2199
|
+
await sleep(lockDelay(attempt, minTimeoutMs, maxTimeoutMs));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
throw new Error(`Failed to acquire memory lock at "${lockPath}".`);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// packages/memory/src/reconcile.ts
|
|
2206
|
+
import { createHash } from "node:crypto";
|
|
2207
|
+
import * as fs5 from "node:fs/promises";
|
|
2208
|
+
import path14 from "node:path";
|
|
2209
|
+
|
|
2210
|
+
// packages/memory/src/confidence.ts
|
|
2211
|
+
var TAG_RE = /^<!--\s*memory:(?<verb>extracted|inferred|ambiguous)(?<rest>[^>]*?)-->\s*$/;
|
|
2212
|
+
function parseClaims(body) {
|
|
2213
|
+
const lines = normalizeNewlines(body).split("\n");
|
|
2214
|
+
const claims = [];
|
|
2215
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2216
|
+
const tagLine = lines[index] ?? "";
|
|
2217
|
+
const match = TAG_RE.exec(tagLine);
|
|
2218
|
+
if (match?.groups?.verb === void 0) {
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
const claimLines = [];
|
|
2222
|
+
for (let claimIndex = index + 1; claimIndex < lines.length; claimIndex += 1) {
|
|
2223
|
+
const line = lines[claimIndex] ?? "";
|
|
2224
|
+
if (line.trim().length === 0 || TAG_RE.test(line)) {
|
|
2225
|
+
break;
|
|
2226
|
+
}
|
|
2227
|
+
claimLines.push(line);
|
|
2228
|
+
}
|
|
2229
|
+
if (claimLines.length === 0) {
|
|
2230
|
+
throw new Error(`Confidence tag on line ${index + 1} is not followed by a claim paragraph.`);
|
|
2231
|
+
}
|
|
2232
|
+
claims.push({
|
|
2233
|
+
tag: parseTag(match.groups.verb, match.groups.rest ?? ""),
|
|
2234
|
+
body: claimLines.join("\n"),
|
|
2235
|
+
lineNumber: index + 1
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
return claims;
|
|
2239
|
+
}
|
|
2240
|
+
function serializeTag(tag) {
|
|
2241
|
+
switch (tag.verb) {
|
|
2242
|
+
case "extracted":
|
|
2243
|
+
return serializeComment("extracted", {
|
|
2244
|
+
source: serializeSourceRef(tag.source),
|
|
2245
|
+
...tag.note === void 0 ? {} : { note: tag.note }
|
|
2246
|
+
});
|
|
2247
|
+
case "inferred":
|
|
2248
|
+
return serializeComment("inferred", {
|
|
2249
|
+
confidence: serializeConfidence(tag.confidence),
|
|
2250
|
+
...tag.source === void 0 ? {} : { source: serializeSourceRef(tag.source) },
|
|
2251
|
+
...tag.note === void 0 ? {} : { note: tag.note }
|
|
2252
|
+
});
|
|
2253
|
+
case "ambiguous": {
|
|
2254
|
+
const reason = tag.reason.trim();
|
|
2255
|
+
if (reason.length === 0) {
|
|
2256
|
+
throw new Error('Ambiguous confidence tags require a non-empty "reason".');
|
|
2257
|
+
}
|
|
2258
|
+
return serializeComment("ambiguous", { reason });
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
function parseTag(verb, rest) {
|
|
2263
|
+
const attrs = parseAttributes(rest);
|
|
2264
|
+
switch (verb) {
|
|
2265
|
+
case "extracted": {
|
|
2266
|
+
const source = attrs.source;
|
|
2267
|
+
if (source === void 0) {
|
|
2268
|
+
throw new Error('Extracted confidence tags require "source".');
|
|
2269
|
+
}
|
|
2270
|
+
assertOnlyKeys(attrs, verb, ["source", "note"]);
|
|
2271
|
+
return {
|
|
2272
|
+
verb,
|
|
2273
|
+
source: parseSourceRef(source),
|
|
2274
|
+
...attrs.note === void 0 ? {} : { note: attrs.note }
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
case "inferred": {
|
|
2278
|
+
const confidence = attrs.confidence;
|
|
2279
|
+
if (confidence === void 0) {
|
|
2280
|
+
throw new Error('Inferred confidence tags require "confidence".');
|
|
2281
|
+
}
|
|
2282
|
+
assertOnlyKeys(attrs, verb, ["confidence", "source", "note"]);
|
|
2283
|
+
return {
|
|
2284
|
+
verb,
|
|
2285
|
+
confidence: parseConfidence(confidence),
|
|
2286
|
+
...attrs.source === void 0 ? {} : { source: parseSourceRef(attrs.source) },
|
|
2287
|
+
...attrs.note === void 0 ? {} : { note: attrs.note }
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
case "ambiguous": {
|
|
2291
|
+
const reason = attrs.reason?.trim();
|
|
2292
|
+
if (reason === void 0 || reason.length === 0) {
|
|
2293
|
+
throw new Error('Ambiguous confidence tags require a non-empty "reason".');
|
|
2294
|
+
}
|
|
2295
|
+
assertOnlyKeys(attrs, verb, ["reason"]);
|
|
2296
|
+
return {
|
|
2297
|
+
verb,
|
|
2298
|
+
reason
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
function parseAttributes(rest) {
|
|
2304
|
+
const attrs = {};
|
|
2305
|
+
let index = 0;
|
|
2306
|
+
while (index < rest.length) {
|
|
2307
|
+
index = skipWhitespace(rest, index);
|
|
2308
|
+
if (index >= rest.length) {
|
|
2309
|
+
break;
|
|
2310
|
+
}
|
|
2311
|
+
const keyStart = index;
|
|
2312
|
+
while (index < rest.length && isKeyCharacter(rest[index] ?? "")) {
|
|
2313
|
+
index += 1;
|
|
2314
|
+
}
|
|
2315
|
+
if (keyStart === index) {
|
|
2316
|
+
throw new Error(`Invalid confidence tag attribute near "${rest.slice(index).trim()}".`);
|
|
2317
|
+
}
|
|
2318
|
+
const key = rest.slice(keyStart, index);
|
|
2319
|
+
if ((rest[index] ?? "") !== "=") {
|
|
2320
|
+
throw new Error(`Invalid confidence tag attribute "${key}".`);
|
|
2321
|
+
}
|
|
2322
|
+
index += 1;
|
|
2323
|
+
const { value, nextIndex } = readAttributeValue(rest, index);
|
|
2324
|
+
if (attrs[key] !== void 0) {
|
|
2325
|
+
throw new Error(`Duplicate confidence tag attribute "${key}".`);
|
|
2326
|
+
}
|
|
2327
|
+
attrs[key] = value;
|
|
2328
|
+
index = nextIndex;
|
|
2329
|
+
}
|
|
2330
|
+
return attrs;
|
|
2331
|
+
}
|
|
2332
|
+
function readAttributeValue(input, index) {
|
|
2333
|
+
if (index >= input.length) {
|
|
2334
|
+
throw new Error("Missing confidence tag attribute value.");
|
|
2335
|
+
}
|
|
2336
|
+
if (input[index] === '"') {
|
|
2337
|
+
const endQuote = findClosingQuote(input, index + 1);
|
|
2338
|
+
return {
|
|
2339
|
+
value: JSON.parse(input.slice(index, endQuote + 1)),
|
|
2340
|
+
nextIndex: endQuote + 1
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
let nextIndex = index;
|
|
2344
|
+
while (nextIndex < input.length && !isWhitespace(input[nextIndex] ?? "")) {
|
|
2345
|
+
nextIndex += 1;
|
|
2346
|
+
}
|
|
2347
|
+
if (nextIndex === index) {
|
|
2348
|
+
throw new Error("Missing confidence tag attribute value.");
|
|
2349
|
+
}
|
|
2350
|
+
return {
|
|
2351
|
+
value: input.slice(index, nextIndex),
|
|
2352
|
+
nextIndex
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
function findClosingQuote(input, start) {
|
|
2356
|
+
let escaped = false;
|
|
2357
|
+
for (let index = start; index < input.length; index += 1) {
|
|
2358
|
+
const char = input[index] ?? "";
|
|
2359
|
+
if (escaped) {
|
|
2360
|
+
escaped = false;
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
if (char === "\\") {
|
|
2364
|
+
escaped = true;
|
|
2365
|
+
continue;
|
|
2366
|
+
}
|
|
2367
|
+
if (char === '"') {
|
|
2368
|
+
return index;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
throw new Error("Unterminated quoted confidence tag attribute.");
|
|
2372
|
+
}
|
|
2373
|
+
function assertOnlyKeys(attrs, verb, allowedKeys) {
|
|
2374
|
+
const disallowedKeys = Object.keys(attrs).filter((key) => !allowedKeys.includes(key));
|
|
2375
|
+
if (disallowedKeys.length > 0) {
|
|
2376
|
+
throw new Error(
|
|
2377
|
+
`${verb} confidence tags do not support: ${disallowedKeys.map((key) => `"${key}"`).join(", ")}.`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function parseConfidence(rawConfidence) {
|
|
2382
|
+
const confidence = Number(rawConfidence);
|
|
2383
|
+
if (!Number.isFinite(confidence) || confidence <= 0 || confidence > 1) {
|
|
2384
|
+
throw new Error(`Invalid confidence value "${rawConfidence}". Expected a number in (0, 1].`);
|
|
2385
|
+
}
|
|
2386
|
+
return confidence;
|
|
2387
|
+
}
|
|
2388
|
+
function serializeComment(verb, attrs) {
|
|
2389
|
+
const parts = Object.entries(attrs).map(([key, value]) => `${key}=${serializeAttributeValue(key, value)}`);
|
|
2390
|
+
return `<!-- memory:${verb}${parts.length === 0 ? "" : ` ${parts.join(" ")}`} -->`;
|
|
2391
|
+
}
|
|
2392
|
+
function serializeAttributeValue(key, value) {
|
|
2393
|
+
return key === "source" || key === "confidence" ? value : JSON.stringify(value);
|
|
2394
|
+
}
|
|
2395
|
+
function serializeConfidence(confidence) {
|
|
2396
|
+
return String(parseConfidence(String(confidence)));
|
|
2397
|
+
}
|
|
2398
|
+
function normalizeNewlines(value) {
|
|
2399
|
+
return value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
2400
|
+
}
|
|
2401
|
+
function skipWhitespace(input, index) {
|
|
2402
|
+
while (index < input.length && isWhitespace(input[index] ?? "")) {
|
|
2403
|
+
index += 1;
|
|
2404
|
+
}
|
|
2405
|
+
return index;
|
|
2406
|
+
}
|
|
2407
|
+
function isKeyCharacter(char) {
|
|
2408
|
+
return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
|
|
2409
|
+
}
|
|
2410
|
+
function isWhitespace(char) {
|
|
2411
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// packages/memory/src/reconcile.ts
|
|
2415
|
+
async function snapshot(root) {
|
|
2416
|
+
const pages = Object.fromEntries(
|
|
2417
|
+
await Promise.all(
|
|
2418
|
+
(await collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH)).map(async (relPath) => [
|
|
2419
|
+
relPath,
|
|
2420
|
+
hashContent(await fs5.readFile(path14.join(root, relPath), "utf8"))
|
|
2421
|
+
])
|
|
2422
|
+
)
|
|
2423
|
+
);
|
|
2424
|
+
return { pages };
|
|
2425
|
+
}
|
|
2426
|
+
async function reconcile(root, before, _verb, detail) {
|
|
2427
|
+
await initMemory(root);
|
|
2428
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2429
|
+
const currentPages = await Promise.all(
|
|
2430
|
+
(await collectMarkdownRelPaths(root, MEMORY_PAGES_DIR_RELPATH)).map(async (relPath) => {
|
|
2431
|
+
const absPath = path14.join(root, relPath);
|
|
2432
|
+
const markdown = await fs5.readFile(absPath, "utf8");
|
|
2433
|
+
const parsed = parsePageMarkdown(relPath, markdown);
|
|
2434
|
+
const normalizedFrontmatter = withDenormalizedSources(parsed.frontmatter, parsed.body);
|
|
2435
|
+
const normalizedMarkdown = serializeFrontmatter(normalizedFrontmatter, parsed.body);
|
|
2436
|
+
const changed = before.pages[relPath] !== hashContent(normalizedMarkdown);
|
|
2437
|
+
const nextMarkdown = changed ? serializeFrontmatter(
|
|
2438
|
+
{
|
|
2439
|
+
...normalizedFrontmatter,
|
|
2440
|
+
lastTouchedAt: timestamp
|
|
2441
|
+
},
|
|
2442
|
+
parsed.body
|
|
2443
|
+
) : normalizedMarkdown;
|
|
2444
|
+
return {
|
|
2445
|
+
relPath,
|
|
2446
|
+
changed,
|
|
2447
|
+
currentMarkdown: markdown,
|
|
2448
|
+
nextMarkdown
|
|
2449
|
+
};
|
|
2450
|
+
})
|
|
2451
|
+
);
|
|
2452
|
+
await Promise.all(
|
|
2453
|
+
currentPages.filter((page) => page.currentMarkdown !== page.nextMarkdown).map((page) => fs5.writeFile(path14.join(root, page.relPath), page.nextMarkdown, "utf8"))
|
|
2454
|
+
);
|
|
2455
|
+
const diff = diffSnapshots(before, await snapshot(root));
|
|
2456
|
+
await writeIndex(root);
|
|
2457
|
+
await appendLogEntries(root, diff, detail, timestamp);
|
|
2458
|
+
return diff;
|
|
2459
|
+
}
|
|
2460
|
+
function renderIndex(entries) {
|
|
2461
|
+
if (entries.length === 0) {
|
|
2462
|
+
return "# Memory index\n";
|
|
2463
|
+
}
|
|
2464
|
+
return [
|
|
2465
|
+
"# Memory index",
|
|
2466
|
+
"",
|
|
2467
|
+
...entries.map(({ relPath, description }) => {
|
|
2468
|
+
const pageName = relPath.slice(`${MEMORY_PAGES_DIR_RELPATH}/`.length).replace(/\.md$/i, "");
|
|
2469
|
+
return description.length === 0 ? `- [${pageName}](${relPath})` : `- [${pageName}](${relPath}) \u2014 ${description}`;
|
|
2470
|
+
}),
|
|
2471
|
+
""
|
|
2472
|
+
].join("\n");
|
|
2473
|
+
}
|
|
2474
|
+
async function appendLogEntries(root, diff, detail, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2475
|
+
const entries = [
|
|
2476
|
+
...diff.updated.map((relPath) => formatLogLine(timestamp, "update", relPath, detail)),
|
|
2477
|
+
...diff.deleted.map((relPath) => formatLogLine(timestamp, "delete", relPath, detail)),
|
|
2478
|
+
...diff.created.map((relPath) => formatLogLine(timestamp, "create", relPath, detail))
|
|
2479
|
+
];
|
|
2480
|
+
if (entries.length === 0) {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
const logPath = path14.join(root, MEMORY_LOG_RELPATH);
|
|
2484
|
+
const existing = await fs5.readFile(logPath, "utf8");
|
|
2485
|
+
const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
2486
|
+
await fs5.writeFile(logPath, `${existing}${separator}${entries.join("\n")}
|
|
2487
|
+
`, "utf8");
|
|
2488
|
+
}
|
|
2489
|
+
function denormalizeSources(markdown) {
|
|
2490
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2491
|
+
for (const claim of parseClaims(parsePageMarkdown("inline-memory-page", markdown).body)) {
|
|
2492
|
+
const source = "source" in claim.tag ? claim.tag.source : void 0;
|
|
2493
|
+
if (source === void 0) {
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
seen.set(serializeSourceRef(source), source);
|
|
2497
|
+
}
|
|
2498
|
+
return Array.from(seen.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([, source]) => source);
|
|
2499
|
+
}
|
|
2500
|
+
async function writeIndex(root) {
|
|
2501
|
+
const index = renderIndex(
|
|
2502
|
+
(await listPages(root)).map((page) => ({
|
|
2503
|
+
relPath: page.relPath,
|
|
2504
|
+
description: page.frontmatter.description ?? ""
|
|
2505
|
+
}))
|
|
2506
|
+
);
|
|
2507
|
+
await fs5.writeFile(path14.join(root, MEMORY_INDEX_RELPATH), index, "utf8");
|
|
2508
|
+
}
|
|
2509
|
+
function parsePageMarkdown(relPath, markdown) {
|
|
2510
|
+
try {
|
|
2511
|
+
return parseFrontmatter(markdown);
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2514
|
+
console.warn(`Failed to parse frontmatter for "${relPath}": ${message}`);
|
|
2515
|
+
return {
|
|
2516
|
+
frontmatter: {},
|
|
2517
|
+
body: markdown
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
function withDenormalizedSources(frontmatter, body) {
|
|
2522
|
+
const sources = denormalizeSources(body);
|
|
2523
|
+
return {
|
|
2524
|
+
...frontmatter,
|
|
2525
|
+
...sources.length === 0 ? { sources: void 0 } : { sources }
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
function diffSnapshots(before, after) {
|
|
2529
|
+
const created = Object.keys(after.pages).filter((relPath) => before.pages[relPath] === void 0).sort((left, right) => left.localeCompare(right));
|
|
2530
|
+
const updated = Object.keys(after.pages).filter(
|
|
2531
|
+
(relPath) => before.pages[relPath] !== void 0 && before.pages[relPath] !== after.pages[relPath]
|
|
2532
|
+
).sort((left, right) => left.localeCompare(right));
|
|
2533
|
+
const deleted = Object.keys(before.pages).filter((relPath) => after.pages[relPath] === void 0).sort((left, right) => left.localeCompare(right));
|
|
2534
|
+
return {
|
|
2535
|
+
created,
|
|
2536
|
+
updated,
|
|
2537
|
+
deleted
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function formatLogLine(timestamp, verb, relPath, detail) {
|
|
2541
|
+
return `- ${timestamp} **${verb}** \`${relPath}\` \u2014 ${detail}`;
|
|
2542
|
+
}
|
|
2543
|
+
function hashContent(content) {
|
|
2544
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// packages/memory/src/write.ts
|
|
2548
|
+
async function writePage(root, relPath, body, opts) {
|
|
2549
|
+
const pageRelPath = assertPageRelPath(relPath);
|
|
2550
|
+
return withLock(root, async () => {
|
|
2551
|
+
const before = await snapshot(root);
|
|
2552
|
+
await fs6.mkdir(path15.dirname(path15.join(root, pageRelPath)), { recursive: true });
|
|
2553
|
+
await fs6.writeFile(
|
|
2554
|
+
path15.join(root, pageRelPath),
|
|
2555
|
+
serializeFrontmatter(opts.frontmatter ?? {}, body),
|
|
2556
|
+
"utf8"
|
|
2557
|
+
);
|
|
2558
|
+
return reconcile(root, before, "update", opts.reason);
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
async function appendToPage(root, relPath, content, opts) {
|
|
2562
|
+
const pageRelPath = assertPageRelPath(relPath);
|
|
2563
|
+
return withLock(root, async () => {
|
|
2564
|
+
const before = await snapshot(root);
|
|
2565
|
+
const pagePath = path15.join(root, pageRelPath);
|
|
2566
|
+
await fs6.mkdir(path15.dirname(pagePath), { recursive: true });
|
|
2567
|
+
const existing = await readMarkdownIfPresent(pagePath);
|
|
2568
|
+
const parsed = existing === void 0 ? { frontmatter: {}, body: "" } : parseFrontmatter(existing);
|
|
2569
|
+
await fs6.writeFile(
|
|
2570
|
+
pagePath,
|
|
2571
|
+
serializeFrontmatter(parsed.frontmatter, `${parsed.body}${content}`),
|
|
2572
|
+
"utf8"
|
|
2573
|
+
);
|
|
2574
|
+
return reconcile(root, before, "update", opts.reason);
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
async function clearMemory(root) {
|
|
2578
|
+
await withLock(root, async () => {
|
|
2579
|
+
await removeChildren(root);
|
|
2580
|
+
await initMemory(root);
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
async function removeChildren(directoryPath) {
|
|
2584
|
+
for (const entryName of await fs6.readdir(directoryPath)) {
|
|
2585
|
+
if (entryName === MEMORY_LOCK_RELPATH) {
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
const entryPath = path15.join(directoryPath, entryName);
|
|
2589
|
+
const stat6 = await fs6.stat(entryPath);
|
|
2590
|
+
if (stat6.isDirectory()) {
|
|
2591
|
+
await removeDirectory2(entryPath);
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (stat6.isFile()) {
|
|
2595
|
+
await fs6.unlink(entryPath);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
async function removeDirectory2(directoryPath) {
|
|
2600
|
+
await removeChildren(directoryPath);
|
|
2601
|
+
await fs6.rmdir(directoryPath);
|
|
2602
|
+
}
|
|
2603
|
+
function assertPageRelPath(relPath) {
|
|
2604
|
+
const normalizedRelPath = assertSafeRelPath(relPath);
|
|
2605
|
+
if (!normalizedRelPath.startsWith(`${MEMORY_PAGES_DIR_RELPATH}/`) || path15.posix.extname(normalizedRelPath).toLowerCase() !== ".md") {
|
|
2606
|
+
throw new Error(`Expected a markdown page path under "${MEMORY_PAGES_DIR_RELPATH}/".`);
|
|
2607
|
+
}
|
|
2608
|
+
return normalizedRelPath;
|
|
2609
|
+
}
|
|
2610
|
+
async function readMarkdownIfPresent(filePath) {
|
|
2611
|
+
try {
|
|
2612
|
+
return await fs6.readFile(filePath, "utf8");
|
|
2613
|
+
} catch (error) {
|
|
2614
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
2615
|
+
return void 0;
|
|
2616
|
+
}
|
|
2617
|
+
throw error;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// packages/memory/src/edit.ts
|
|
2622
|
+
async function editPage(root, relPath, opts) {
|
|
2623
|
+
const pagePath = path16.join(root, relPath);
|
|
2624
|
+
const original = await readIfPresent(pagePath);
|
|
2625
|
+
const tempRoot = path16.join(root, ".tmp");
|
|
2626
|
+
await fs7.mkdir(tempRoot, { recursive: true });
|
|
2627
|
+
const tempDir = await fs7.mkdtemp(path16.join(tempRoot, "poe-code-memory-edit-"));
|
|
2628
|
+
const tempPath = path16.join(tempDir, path16.basename(relPath));
|
|
2629
|
+
try {
|
|
2630
|
+
await fs7.writeFile(tempPath, original ?? "", "utf8");
|
|
2631
|
+
await opts.launchEditor(tempPath);
|
|
2632
|
+
const edited = await fs7.readFile(tempPath, "utf8");
|
|
2633
|
+
if (edited === (original ?? "")) {
|
|
2634
|
+
return { changed: false };
|
|
2635
|
+
}
|
|
2636
|
+
const parsed = parseFrontmatter(edited);
|
|
2637
|
+
const diff = await writePage(root, relPath, parsed.body, {
|
|
2638
|
+
frontmatter: parsed.frontmatter,
|
|
2639
|
+
reason: opts.reason
|
|
2640
|
+
});
|
|
2641
|
+
return {
|
|
2642
|
+
changed: true,
|
|
2643
|
+
diff
|
|
2644
|
+
};
|
|
2645
|
+
} finally {
|
|
2646
|
+
await fs7.rm(tempDir, { recursive: true, force: true });
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
async function readIfPresent(filePath) {
|
|
2650
|
+
try {
|
|
2651
|
+
return await fs7.readFile(filePath, "utf8");
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
2654
|
+
return void 0;
|
|
2655
|
+
}
|
|
2656
|
+
throw error;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
// packages/memory/src/audit.ts
|
|
2661
|
+
import * as fs8 from "node:fs/promises";
|
|
2662
|
+
import path17 from "node:path";
|
|
2663
|
+
var DEFAULT_MIN_INFERRED_CONFIDENCE = 0.3;
|
|
2664
|
+
var DEFAULT_REJECT_UNTAGGED = false;
|
|
2665
|
+
var DEFAULT_UNTAGGED_BODY_THRESHOLD_CHARS = 200;
|
|
2666
|
+
async function auditClaims(root, repoRoot, options = {}) {
|
|
2667
|
+
const minInferredConfidence = options.minInferredConfidence ?? DEFAULT_MIN_INFERRED_CONFIDENCE;
|
|
2668
|
+
const rejectUntagged = options.rejectUntagged ?? DEFAULT_REJECT_UNTAGGED;
|
|
2669
|
+
const untaggedBodyThresholdChars = options.untaggedBodyThresholdChars ?? DEFAULT_UNTAGGED_BODY_THRESHOLD_CHARS;
|
|
2670
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
2671
|
+
const pages = await listPages(root);
|
|
2672
|
+
const audits = [];
|
|
2673
|
+
for (const page of pages) {
|
|
2674
|
+
const issues = [];
|
|
2675
|
+
let claims;
|
|
2676
|
+
try {
|
|
2677
|
+
claims = parseClaims(page.body);
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
issues.push(formatError(error));
|
|
2680
|
+
audits.push({ page: page.relPath, issues });
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
if (rejectUntagged && claims.length === 0 && page.body.trim().length > untaggedBodyThresholdChars) {
|
|
2684
|
+
issues.push(
|
|
2685
|
+
`Page has a long untagged body (>${untaggedBodyThresholdChars} chars) with no memory:* tags.`
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
const inlineSources = /* @__PURE__ */ new Set();
|
|
2689
|
+
for (const claim of claims) {
|
|
2690
|
+
if (claim.tag.verb === "inferred" && claim.tag.confidence < minInferredConfidence) {
|
|
2691
|
+
issues.push(
|
|
2692
|
+
`Claim on line ${claim.lineNumber} uses inferred confidence=${claim.tag.confidence}, below the minimum ${minInferredConfidence}.`
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
const source = "source" in claim.tag ? claim.tag.source : void 0;
|
|
2696
|
+
if (source === void 0) {
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
const serializedSource = serializeSourceRef(source);
|
|
2700
|
+
inlineSources.add(serializedSource);
|
|
2701
|
+
const sourceIssue = await auditSourceRef(source, claim.lineNumber, repoRoot, sourceCache);
|
|
2702
|
+
if (sourceIssue !== void 0) {
|
|
2703
|
+
issues.push(sourceIssue);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
issues.push(...auditFrontmatterSources(page.frontmatter.sources ?? [], inlineSources));
|
|
2707
|
+
if (issues.length > 0) {
|
|
2708
|
+
audits.push({ page: page.relPath, issues });
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
return audits;
|
|
2712
|
+
}
|
|
2713
|
+
async function auditSourceRef(source, claimLineNumber, repoRoot, sourceCache) {
|
|
2714
|
+
if (isUrlLike(source.path)) {
|
|
2715
|
+
return void 0;
|
|
2716
|
+
}
|
|
2717
|
+
if (path17.isAbsolute(source.path)) {
|
|
2718
|
+
return `Claim on line ${claimLineNumber} cites "${serializeSourceRef(source)}", but source paths must be repo-relative or URLs.`;
|
|
2719
|
+
}
|
|
2720
|
+
const absPath = path17.resolve(repoRoot, source.path);
|
|
2721
|
+
if (!isWithinRoot(repoRoot, absPath)) {
|
|
2722
|
+
return `Claim on line ${claimLineNumber} cites "${serializeSourceRef(source)}", which resolves outside the repo root.`;
|
|
2723
|
+
}
|
|
2724
|
+
const meta = await readSourceFile(absPath, sourceCache);
|
|
2725
|
+
if (!meta.exists) {
|
|
2726
|
+
return `Claim on line ${claimLineNumber} cites "${serializeSourceRef(source)}", resolved to "${meta.absPath}", but the file does not exist.`;
|
|
2727
|
+
}
|
|
2728
|
+
const lastReferencedLine = source.endLine ?? source.startLine;
|
|
2729
|
+
if (lastReferencedLine !== void 0 && lastReferencedLine > meta.lineCount) {
|
|
2730
|
+
return `Claim on line ${claimLineNumber} cites "${serializeSourceRef(source)}", but current EOF ${meta.lineCount} is before the referenced line.`;
|
|
2731
|
+
}
|
|
2732
|
+
return void 0;
|
|
2733
|
+
}
|
|
2734
|
+
function auditFrontmatterSources(frontmatterSources, inlineSources) {
|
|
2735
|
+
const serializedFrontmatterSources = new Set(frontmatterSources.map((source) => serializeSourceRef(source)));
|
|
2736
|
+
const issues = [];
|
|
2737
|
+
const missingSources = [...inlineSources].filter((source) => !serializedFrontmatterSources.has(source));
|
|
2738
|
+
for (const source of missingSources.sort((left, right) => left.localeCompare(right))) {
|
|
2739
|
+
issues.push(`Page frontmatter sources are missing "${source}" from inline tags.`);
|
|
2740
|
+
}
|
|
2741
|
+
const staleSources = [...serializedFrontmatterSources].filter((source) => !inlineSources.has(source));
|
|
2742
|
+
for (const source of staleSources.sort((left, right) => left.localeCompare(right))) {
|
|
2743
|
+
issues.push(`Page frontmatter sources contain stale entry "${source}" not found in inline tags.`);
|
|
2744
|
+
}
|
|
2745
|
+
return issues;
|
|
2746
|
+
}
|
|
2747
|
+
function readSourceFile(absPath, sourceCache) {
|
|
2748
|
+
const cached = sourceCache.get(absPath);
|
|
2749
|
+
if (cached !== void 0) {
|
|
2750
|
+
return cached;
|
|
2751
|
+
}
|
|
2752
|
+
const pending = fs8.readFile(absPath, "utf8").then((content) => ({
|
|
2753
|
+
exists: true,
|
|
2754
|
+
absPath,
|
|
2755
|
+
lineCount: countLines(content)
|
|
2756
|
+
})).catch((error) => {
|
|
2757
|
+
if (isMissing2(error)) {
|
|
2758
|
+
return {
|
|
2759
|
+
exists: false,
|
|
2760
|
+
absPath
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
throw error;
|
|
2764
|
+
});
|
|
2765
|
+
sourceCache.set(absPath, pending);
|
|
2766
|
+
return pending;
|
|
2767
|
+
}
|
|
2768
|
+
function countLines(content) {
|
|
2769
|
+
if (content.length === 0) {
|
|
2770
|
+
return 0;
|
|
2771
|
+
}
|
|
2772
|
+
const normalized = content.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
2773
|
+
const trimmed = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
|
|
2774
|
+
return trimmed.length === 0 ? 0 : trimmed.split("\n").length;
|
|
2775
|
+
}
|
|
2776
|
+
function formatError(error) {
|
|
2777
|
+
return error instanceof Error ? error.message : String(error);
|
|
2778
|
+
}
|
|
2779
|
+
function isMissing2(error) {
|
|
2780
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2781
|
+
}
|
|
2782
|
+
function isUrlLike(value) {
|
|
2783
|
+
return /^[a-z][a-z\d+.-]*:\/\//i.test(value);
|
|
2784
|
+
}
|
|
2785
|
+
function isWithinRoot(root, absPath) {
|
|
2786
|
+
const relative = path17.relative(root, absPath);
|
|
2787
|
+
return relative === "" || !relative.startsWith("..") && !path17.isAbsolute(relative);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// packages/memory/src/cache.ts
|
|
2791
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2792
|
+
import * as fs9 from "node:fs/promises";
|
|
2793
|
+
import path18 from "node:path";
|
|
2794
|
+
function computeIngestKey(input) {
|
|
2795
|
+
const hash = createHash2("sha256");
|
|
2796
|
+
hash.update(input.sourceBytes);
|
|
2797
|
+
hash.update("\0");
|
|
2798
|
+
hash.update(input.indexMdBytes);
|
|
2799
|
+
hash.update("\0");
|
|
2800
|
+
hash.update(input.promptTemplateVersion);
|
|
2801
|
+
hash.update("\0");
|
|
2802
|
+
hash.update(input.agentId);
|
|
2803
|
+
return hash.digest("hex");
|
|
2804
|
+
}
|
|
2805
|
+
async function readCacheEntry(root, key) {
|
|
2806
|
+
const cachePath = path18.join(root, MEMORY_INGEST_CACHE_DIR_RELPATH, `${key}.json`);
|
|
2807
|
+
let raw;
|
|
2808
|
+
try {
|
|
2809
|
+
raw = await fs9.readFile(cachePath, "utf8");
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
if (isMissing3(error)) {
|
|
2812
|
+
return null;
|
|
2813
|
+
}
|
|
2814
|
+
throw error;
|
|
2815
|
+
}
|
|
2816
|
+
try {
|
|
2817
|
+
return parseCacheEntry(JSON.parse(raw), key);
|
|
2818
|
+
} catch (error) {
|
|
2819
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2820
|
+
console.warn(`Ignoring ingest cache entry "${key}": ${message}`);
|
|
2821
|
+
return null;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
async function writeCacheEntry(root, entry) {
|
|
2825
|
+
await fs9.mkdir(path18.join(root, MEMORY_INGEST_CACHE_DIR_RELPATH), { recursive: true });
|
|
2826
|
+
await fs9.writeFile(
|
|
2827
|
+
path18.join(root, MEMORY_INGEST_CACHE_DIR_RELPATH, `${entry.key}.json`),
|
|
2828
|
+
`${JSON.stringify(entry)}
|
|
2829
|
+
`,
|
|
2830
|
+
"utf8"
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
async function clearCache(root, opts = {}) {
|
|
2834
|
+
const ingestDir = path18.join(root, MEMORY_INGEST_CACHE_DIR_RELPATH);
|
|
2835
|
+
const cacheDir = path18.join(root, MEMORY_CACHE_DIR_RELPATH);
|
|
2836
|
+
const fileNames = await readCacheFileNames(ingestDir);
|
|
2837
|
+
if (fileNames.length === 0) {
|
|
2838
|
+
if (opts.olderThanMs === void 0) {
|
|
2839
|
+
await fs9.rm(cacheDir, { recursive: true, force: true });
|
|
2840
|
+
}
|
|
2841
|
+
return { removed: 0 };
|
|
2842
|
+
}
|
|
2843
|
+
if (opts.olderThanMs === void 0) {
|
|
2844
|
+
await fs9.rm(cacheDir, { recursive: true, force: true });
|
|
2845
|
+
return { removed: fileNames.length };
|
|
2846
|
+
}
|
|
2847
|
+
const cutoff = Date.now() - opts.olderThanMs;
|
|
2848
|
+
let removed = 0;
|
|
2849
|
+
for (const fileName of fileNames) {
|
|
2850
|
+
const key = fileName.slice(0, -".json".length);
|
|
2851
|
+
const entry = await readCacheEntry(root, key);
|
|
2852
|
+
if (entry === null || Date.parse(entry.ingestedAt) > cutoff) {
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
await fs9.rm(path18.join(ingestDir, fileName), { force: true });
|
|
2856
|
+
removed += 1;
|
|
2857
|
+
}
|
|
2858
|
+
await removeEmptyDirectory(ingestDir);
|
|
2859
|
+
await removeEmptyDirectory(cacheDir);
|
|
2860
|
+
return { removed };
|
|
2861
|
+
}
|
|
2862
|
+
function parseCacheEntry(value, key) {
|
|
2863
|
+
const object = expectRecord(value);
|
|
2864
|
+
return {
|
|
2865
|
+
key: expectString(object.key, "key"),
|
|
2866
|
+
ingestedAt: expectString(object.ingestedAt, "ingestedAt"),
|
|
2867
|
+
sourceLabel: expectString(object.sourceLabel, "sourceLabel"),
|
|
2868
|
+
diff: parseMemoryDiff(object.diff),
|
|
2869
|
+
exitCode: expectNumber(object.exitCode, "exitCode"),
|
|
2870
|
+
durationMs: expectNumber(object.durationMs, "durationMs"),
|
|
2871
|
+
memoryTokens: expectNumber(object.memoryTokens, "memoryTokens"),
|
|
2872
|
+
sourceTokens: expectNumber(object.sourceTokens, "sourceTokens"),
|
|
2873
|
+
promptTemplateVersion: expectString(object.promptTemplateVersion, "promptTemplateVersion"),
|
|
2874
|
+
agentId: expectString(object.agentId, "agentId")
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
function parseMemoryDiff(value) {
|
|
2878
|
+
const object = expectRecord(value);
|
|
2879
|
+
return {
|
|
2880
|
+
created: expectStringArray(object.created, "diff.created"),
|
|
2881
|
+
updated: expectStringArray(object.updated, "diff.updated"),
|
|
2882
|
+
deleted: expectStringArray(object.deleted, "diff.deleted")
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
function expectRecord(value) {
|
|
2886
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2887
|
+
throw new Error("Expected a JSON object.");
|
|
2888
|
+
}
|
|
2889
|
+
return value;
|
|
2890
|
+
}
|
|
2891
|
+
function expectString(value, field) {
|
|
2892
|
+
if (typeof value !== "string") {
|
|
2893
|
+
throw new Error(`Expected string at "${field}".`);
|
|
2894
|
+
}
|
|
2895
|
+
return value;
|
|
2896
|
+
}
|
|
2897
|
+
function expectNumber(value, field) {
|
|
2898
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
2899
|
+
throw new Error(`Expected number at "${field}".`);
|
|
2900
|
+
}
|
|
2901
|
+
return value;
|
|
2902
|
+
}
|
|
2903
|
+
function expectStringArray(value, field) {
|
|
2904
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
2905
|
+
throw new Error(`Expected string[] at "${field}".`);
|
|
2906
|
+
}
|
|
2907
|
+
return value;
|
|
2908
|
+
}
|
|
2909
|
+
async function readCacheFileNames(ingestDir) {
|
|
2910
|
+
try {
|
|
2911
|
+
return (await fs9.readdir(ingestDir)).filter((fileName) => path18.posix.extname(fileName).toLowerCase() === ".json").sort((left, right) => left.localeCompare(right));
|
|
2912
|
+
} catch (error) {
|
|
2913
|
+
if (isMissing3(error)) {
|
|
2914
|
+
return [];
|
|
2915
|
+
}
|
|
2916
|
+
throw error;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
async function removeEmptyDirectory(directoryPath) {
|
|
2920
|
+
try {
|
|
2921
|
+
const remainingEntries = await fs9.readdir(directoryPath);
|
|
2922
|
+
if (remainingEntries.length === 0) {
|
|
2923
|
+
await fs9.rmdir(directoryPath);
|
|
2924
|
+
}
|
|
2925
|
+
} catch (error) {
|
|
2926
|
+
if (isMissing3(error)) {
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
throw error;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
function isMissing3(error) {
|
|
2933
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// packages/memory/src/cache.cli.ts
|
|
2937
|
+
import parseDuration from "parse-duration";
|
|
2938
|
+
async function runMemoryCacheStatus() {
|
|
2939
|
+
console.log("cache status not implemented yet");
|
|
2940
|
+
}
|
|
2941
|
+
async function runMemoryCacheClear(input) {
|
|
2942
|
+
if (!input.yes) {
|
|
2943
|
+
throw new Error("Refusing to clear cache without --yes.");
|
|
2944
|
+
}
|
|
2945
|
+
const olderThanMs = parseOlderThan(input.olderThan);
|
|
2946
|
+
const result = await clearCache(input.root, olderThanMs === void 0 ? {} : { olderThanMs });
|
|
2947
|
+
console.log(`removed ${result.removed} cache ${result.removed === 1 ? "entry" : "entries"}`);
|
|
2948
|
+
return result;
|
|
2949
|
+
}
|
|
2950
|
+
function parseOlderThan(value) {
|
|
2951
|
+
if (value === void 0) {
|
|
2952
|
+
return void 0;
|
|
2953
|
+
}
|
|
2954
|
+
const duration = parseDuration(value);
|
|
2955
|
+
if (duration === null || Number.isNaN(duration) || duration < 0) {
|
|
2956
|
+
throw new Error(`Invalid duration for --older-than: "${value}".`);
|
|
2957
|
+
}
|
|
2958
|
+
return duration;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// packages/memory/src/ingest.ts
|
|
2962
|
+
import * as fs11 from "node:fs/promises";
|
|
2963
|
+
import path20 from "node:path";
|
|
2964
|
+
|
|
2965
|
+
// packages/memory/src/tokens.ts
|
|
2966
|
+
import * as fs10 from "node:fs/promises";
|
|
2967
|
+
import path19 from "node:path";
|
|
2968
|
+
|
|
2969
|
+
// packages/tokenfill/src/tokenizer.ts
|
|
2970
|
+
import { get_encoding } from "tiktoken";
|
|
2971
|
+
var DEFAULT_ENCODING = "cl100k_base";
|
|
2972
|
+
function createTokenizer(options = {}) {
|
|
2973
|
+
const encoding = options.encoding ?? DEFAULT_ENCODING;
|
|
2974
|
+
const tokenizer = get_encoding(encoding);
|
|
2975
|
+
const utf8Decoder = new TextDecoder();
|
|
2976
|
+
const encode = (text) => tokenizer.encode(text);
|
|
2977
|
+
const decode = (tokens) => {
|
|
2978
|
+
const tokenArray = tokens instanceof Uint32Array ? tokens : Uint32Array.from(tokens);
|
|
2979
|
+
return utf8Decoder.decode(tokenizer.decode(tokenArray));
|
|
2980
|
+
};
|
|
2981
|
+
const count = (text) => encode(text).length;
|
|
2982
|
+
const truncate = (text, tokenCount) => {
|
|
2983
|
+
if (tokenCount <= 0) {
|
|
2984
|
+
return "";
|
|
2985
|
+
}
|
|
2986
|
+
const tokens = encode(text);
|
|
2987
|
+
if (tokens.length <= tokenCount) {
|
|
2988
|
+
return text;
|
|
2989
|
+
}
|
|
2990
|
+
return decode(tokens.slice(0, tokenCount));
|
|
2991
|
+
};
|
|
2992
|
+
return {
|
|
2993
|
+
encoding,
|
|
2994
|
+
encode,
|
|
2995
|
+
decode,
|
|
2996
|
+
count,
|
|
2997
|
+
truncate,
|
|
2998
|
+
free: () => tokenizer.free()
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
var defaultTokenizer;
|
|
3002
|
+
function countTokens(text) {
|
|
3003
|
+
defaultTokenizer ??= createTokenizer();
|
|
3004
|
+
return defaultTokenizer.count(text);
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// packages/tokenfill/src/corpus.ts
|
|
3008
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3009
|
+
import { dirname, join } from "node:path";
|
|
3010
|
+
import { fileURLToPath } from "node:url";
|
|
3011
|
+
var CORPUS_ARTICLE_SEPARATOR = "\n\n";
|
|
3012
|
+
var corpusDirectoryPath = join(dirname(fileURLToPath(import.meta.url)), "corpus");
|
|
3013
|
+
function getCorpusFileNames() {
|
|
3014
|
+
return readdirSync(corpusDirectoryPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
3015
|
+
}
|
|
3016
|
+
function loadBuiltInCorpusArticles() {
|
|
3017
|
+
const corpusFileNames = getCorpusFileNames();
|
|
3018
|
+
if (corpusFileNames.length === 0) {
|
|
3019
|
+
throw new Error(`No built-in corpus markdown files found in ${corpusDirectoryPath}`);
|
|
3020
|
+
}
|
|
3021
|
+
return corpusFileNames.map((fileName) => readFileSync(join(corpusDirectoryPath, fileName), "utf8").trim());
|
|
3022
|
+
}
|
|
3023
|
+
var BUILT_IN_CORPUS_ARTICLES = loadBuiltInCorpusArticles();
|
|
3024
|
+
|
|
3025
|
+
// packages/tokenfill/src/tokenfill.ts
|
|
3026
|
+
var builtInCorpusText = BUILT_IN_CORPUS_ARTICLES.join(CORPUS_ARTICLE_SEPARATOR);
|
|
3027
|
+
var builtInCorpusByteLength = Buffer.byteLength(builtInCorpusText, "utf8");
|
|
3028
|
+
|
|
3029
|
+
// packages/memory/src/tokens.ts
|
|
3030
|
+
async function computeTokenStats(root) {
|
|
3031
|
+
if (!await pathExists3(root)) {
|
|
3032
|
+
return {
|
|
3033
|
+
memoryTokens: 0,
|
|
3034
|
+
sourceTokens: 0,
|
|
3035
|
+
reductionRatio: 0,
|
|
3036
|
+
missingSources: []
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
const pages = await listPages(root);
|
|
3040
|
+
let memoryTokens = 0;
|
|
3041
|
+
const sourcePaths = /* @__PURE__ */ new Set();
|
|
3042
|
+
for (const page of pages) {
|
|
3043
|
+
memoryTokens += countTokens(page.body);
|
|
3044
|
+
for (const source of page.frontmatter.sources ?? []) {
|
|
3045
|
+
const normalized = source.path.trim();
|
|
3046
|
+
if (normalized.length > 0) {
|
|
3047
|
+
sourcePaths.add(normalized);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
const repoRoot = path19.resolve(root, "..", "..");
|
|
3052
|
+
let sourceTokens = 0;
|
|
3053
|
+
const missingSources = [];
|
|
3054
|
+
for (const sourcePath of sourcePaths) {
|
|
3055
|
+
const absPath = path19.isAbsolute(sourcePath) ? sourcePath : path19.resolve(repoRoot, sourcePath);
|
|
3056
|
+
try {
|
|
3057
|
+
const content = await fs10.readFile(absPath, "utf8");
|
|
3058
|
+
sourceTokens += countTokens(content);
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
if (isMissing4(error)) {
|
|
3061
|
+
missingSources.push(sourcePath);
|
|
3062
|
+
continue;
|
|
3063
|
+
}
|
|
3064
|
+
throw error;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
missingSources.sort((left, right) => left.localeCompare(right));
|
|
3068
|
+
const reductionRatio = sourceTokens === 0 ? 0 : sourceTokens / Math.max(memoryTokens, 1);
|
|
3069
|
+
return {
|
|
3070
|
+
memoryTokens,
|
|
3071
|
+
sourceTokens,
|
|
3072
|
+
reductionRatio,
|
|
3073
|
+
missingSources
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
async function pathExists3(targetPath) {
|
|
3077
|
+
try {
|
|
3078
|
+
await fs10.stat(targetPath);
|
|
3079
|
+
return true;
|
|
3080
|
+
} catch (error) {
|
|
3081
|
+
if (isMissing4(error)) {
|
|
3082
|
+
return false;
|
|
3083
|
+
}
|
|
3084
|
+
throw error;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
function isMissing4(error) {
|
|
3088
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// packages/memory/src/ingest.ts
|
|
3092
|
+
var INGEST_PROMPT_VERSION = "v1";
|
|
3093
|
+
async function ingest(root, opts) {
|
|
3094
|
+
const source = await materializeSource(opts.source);
|
|
3095
|
+
const indexMdBytes = await fs11.readFile(path20.join(root, MEMORY_INDEX_RELPATH));
|
|
3096
|
+
const configOptions = {
|
|
3097
|
+
fs: fs11,
|
|
3098
|
+
filePath: path20.join(inferRepoRoot(root), "poe-code.json")
|
|
3099
|
+
};
|
|
3100
|
+
const agentId = await resolveAgent(configOptions, opts.agent ?? null) ?? opts.agent ?? "claude-code";
|
|
3101
|
+
const key = computeIngestKey({
|
|
3102
|
+
sourceBytes: source.bytes,
|
|
3103
|
+
indexMdBytes,
|
|
3104
|
+
promptTemplateVersion: INGEST_PROMPT_VERSION,
|
|
3105
|
+
agentId
|
|
3106
|
+
});
|
|
3107
|
+
if (!opts.force && await cacheEnabled(configOptions)) {
|
|
3108
|
+
const hit = await readCacheEntry(root, key);
|
|
3109
|
+
if (hit !== null) {
|
|
3110
|
+
return {
|
|
3111
|
+
diff: { created: [], updated: [], deleted: [] },
|
|
3112
|
+
exitCode: 0,
|
|
3113
|
+
durationMs: 0,
|
|
3114
|
+
cacheHit: true,
|
|
3115
|
+
tokens: await computeTokenStats(root)
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const prompt = buildIngestPrompt(root, source.label, source.text);
|
|
3120
|
+
if (opts.dryRun) {
|
|
3121
|
+
console.log(prompt);
|
|
3122
|
+
return {
|
|
3123
|
+
diff: { created: [], updated: [], deleted: [] },
|
|
3124
|
+
exitCode: 0,
|
|
3125
|
+
durationMs: 0,
|
|
3126
|
+
cacheHit: false,
|
|
3127
|
+
tokens: await computeTokenStats(root)
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
const before = await snapshot(root);
|
|
3131
|
+
let exitCode = 1;
|
|
3132
|
+
let durationMs = 0;
|
|
3133
|
+
let timeoutError;
|
|
3134
|
+
try {
|
|
3135
|
+
const spawnFn = opts.spawnFn;
|
|
3136
|
+
const result = await runWithTimeout(
|
|
3137
|
+
spawnFn?.(agentId, prompt) ?? Promise.resolve({ exitCode: 0, durationMs: 0 }),
|
|
3138
|
+
opts.timeoutMs ?? await configuredTimeout(configOptions)
|
|
3139
|
+
);
|
|
3140
|
+
exitCode = result.exitCode;
|
|
3141
|
+
durationMs = result.durationMs;
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
timeoutError = error instanceof Error ? error : new Error(String(error));
|
|
3144
|
+
}
|
|
3145
|
+
const diff = await reconcile(root, before, "ingest", opts.reason ?? `ingest ${source.label}`);
|
|
3146
|
+
const tokens = await computeTokenStats(root);
|
|
3147
|
+
if (timeoutError !== void 0) {
|
|
3148
|
+
throw timeoutError;
|
|
3149
|
+
}
|
|
3150
|
+
if (!opts.noCacheWrite && await cacheEnabled(configOptions) && exitCode === 0) {
|
|
3151
|
+
await writeCacheEntry(root, {
|
|
3152
|
+
key,
|
|
3153
|
+
ingestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3154
|
+
sourceLabel: source.label,
|
|
3155
|
+
diff,
|
|
3156
|
+
exitCode,
|
|
3157
|
+
durationMs,
|
|
3158
|
+
memoryTokens: tokens.memoryTokens,
|
|
3159
|
+
sourceTokens: tokens.sourceTokens,
|
|
3160
|
+
promptTemplateVersion: INGEST_PROMPT_VERSION,
|
|
3161
|
+
agentId
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
return { diff, exitCode, durationMs, cacheHit: false, tokens };
|
|
3165
|
+
}
|
|
3166
|
+
function buildIngestPrompt(root, sourceLabel, sourceText) {
|
|
3167
|
+
return [
|
|
3168
|
+
`Prompt version: ${INGEST_PROMPT_VERSION}`,
|
|
3169
|
+
`Memory root: ${root}`,
|
|
3170
|
+
`Source: ${sourceLabel}`,
|
|
3171
|
+
"Update memory pages under pages/ only. Do not edit INDEX.md directly.",
|
|
3172
|
+
"Add confidence tags to non-trivial claims.",
|
|
3173
|
+
"",
|
|
3174
|
+
sourceText
|
|
3175
|
+
].join("\n");
|
|
3176
|
+
}
|
|
3177
|
+
async function materializeSource(source) {
|
|
3178
|
+
if (source.kind === "file") {
|
|
3179
|
+
const bytes = await fs11.readFile(source.absPath);
|
|
3180
|
+
return {
|
|
3181
|
+
label: source.absPath,
|
|
3182
|
+
bytes,
|
|
3183
|
+
text: bytes.toString("utf8")
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
throw new Error("URL ingest not implemented yet.");
|
|
3187
|
+
}
|
|
3188
|
+
function inferRepoRoot(root) {
|
|
3189
|
+
return path20.resolve(root, "..", "..");
|
|
3190
|
+
}
|
|
3191
|
+
async function runWithTimeout(promise, timeoutMs) {
|
|
3192
|
+
return await new Promise((resolve2, reject) => {
|
|
3193
|
+
const timer = setTimeout(() => {
|
|
3194
|
+
reject(new Error(`ingest timed out after ${timeoutMs}ms`));
|
|
3195
|
+
}, timeoutMs);
|
|
3196
|
+
promise.then(
|
|
3197
|
+
(value) => {
|
|
3198
|
+
clearTimeout(timer);
|
|
3199
|
+
resolve2(value);
|
|
3200
|
+
},
|
|
3201
|
+
(error) => {
|
|
3202
|
+
clearTimeout(timer);
|
|
3203
|
+
reject(error);
|
|
3204
|
+
}
|
|
3205
|
+
);
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
// packages/tiny-stdio-mcp-server/src/server.ts
|
|
3210
|
+
import * as readline from "readline";
|
|
3211
|
+
|
|
3212
|
+
// packages/tiny-stdio-mcp-server/src/types.ts
|
|
3213
|
+
var JSON_RPC_ERROR_CODES = {
|
|
3214
|
+
PARSE_ERROR: -32700,
|
|
3215
|
+
INVALID_REQUEST: -32600,
|
|
3216
|
+
METHOD_NOT_FOUND: -32601,
|
|
3217
|
+
INVALID_PARAMS: -32602,
|
|
3218
|
+
INTERNAL_ERROR: -32603
|
|
3219
|
+
};
|
|
3220
|
+
var ToolError = class extends Error {
|
|
3221
|
+
constructor(code, message) {
|
|
3222
|
+
super(message);
|
|
3223
|
+
this.code = code;
|
|
3224
|
+
this.name = "ToolError";
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
|
|
3228
|
+
// packages/tiny-stdio-mcp-server/src/jsonrpc.ts
|
|
3229
|
+
function parseMessage(line) {
|
|
3230
|
+
let parsed;
|
|
3231
|
+
try {
|
|
3232
|
+
parsed = JSON.parse(line);
|
|
3233
|
+
} catch {
|
|
3234
|
+
return {
|
|
3235
|
+
success: false,
|
|
3236
|
+
error: {
|
|
3237
|
+
code: JSON_RPC_ERROR_CODES.PARSE_ERROR,
|
|
3238
|
+
message: "Parse error"
|
|
3239
|
+
},
|
|
3240
|
+
id: null
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3244
|
+
return {
|
|
3245
|
+
success: false,
|
|
3246
|
+
error: {
|
|
3247
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
3248
|
+
message: "Invalid Request"
|
|
3249
|
+
},
|
|
3250
|
+
id: null
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
const obj = parsed;
|
|
3254
|
+
const hasId = "id" in obj;
|
|
3255
|
+
const id = typeof obj.id === "string" || typeof obj.id === "number" ? obj.id : null;
|
|
3256
|
+
if (obj.jsonrpc !== "2.0") {
|
|
3257
|
+
return {
|
|
3258
|
+
success: false,
|
|
3259
|
+
error: {
|
|
3260
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
3261
|
+
message: "Invalid Request"
|
|
3262
|
+
},
|
|
3263
|
+
id
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
if (typeof obj.method !== "string") {
|
|
3267
|
+
return {
|
|
3268
|
+
success: false,
|
|
3269
|
+
error: {
|
|
3270
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
3271
|
+
message: "Invalid Request"
|
|
3272
|
+
},
|
|
3273
|
+
id
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
if (!hasId) {
|
|
3277
|
+
return {
|
|
3278
|
+
success: true,
|
|
3279
|
+
isNotification: true,
|
|
3280
|
+
request: {
|
|
3281
|
+
jsonrpc: "2.0",
|
|
3282
|
+
method: obj.method,
|
|
3283
|
+
params: obj.params
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
}
|
|
3287
|
+
if (id === null) {
|
|
3288
|
+
return {
|
|
3289
|
+
success: false,
|
|
3290
|
+
error: {
|
|
3291
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
3292
|
+
message: "Invalid Request"
|
|
3293
|
+
},
|
|
3294
|
+
id: null
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
return {
|
|
3298
|
+
success: true,
|
|
3299
|
+
isNotification: false,
|
|
3300
|
+
request: {
|
|
3301
|
+
jsonrpc: "2.0",
|
|
3302
|
+
id,
|
|
3303
|
+
method: obj.method,
|
|
3304
|
+
params: obj.params
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
function formatSuccessResponse(id, result) {
|
|
3309
|
+
const response = {
|
|
3310
|
+
jsonrpc: "2.0",
|
|
3311
|
+
id,
|
|
3312
|
+
result
|
|
3313
|
+
};
|
|
3314
|
+
return JSON.stringify(response);
|
|
3315
|
+
}
|
|
3316
|
+
function formatErrorResponse(id, error) {
|
|
3317
|
+
const response = {
|
|
3318
|
+
jsonrpc: "2.0",
|
|
3319
|
+
id,
|
|
3320
|
+
error
|
|
3321
|
+
};
|
|
3322
|
+
return JSON.stringify(response);
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
// packages/tiny-stdio-mcp-server/src/content/file-type.ts
|
|
3326
|
+
function fileTypeFromBuffer(data) {
|
|
3327
|
+
if (data.length < 12) {
|
|
3328
|
+
return void 0;
|
|
3329
|
+
}
|
|
3330
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10) {
|
|
3331
|
+
return { mime: "image/png", ext: "png" };
|
|
3332
|
+
}
|
|
3333
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) {
|
|
3334
|
+
return { mime: "image/jpeg", ext: "jpg" };
|
|
3335
|
+
}
|
|
3336
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70 && data[3] === 56) {
|
|
3337
|
+
return { mime: "image/gif", ext: "gif" };
|
|
3338
|
+
}
|
|
3339
|
+
if (data[0] === 82 && data[1] === 73 && data[2] === 70 && data[3] === 70 && data[8] === 87 && data[9] === 69 && data[10] === 66 && data[11] === 80) {
|
|
3340
|
+
return { mime: "image/webp", ext: "webp" };
|
|
3341
|
+
}
|
|
3342
|
+
if (data[0] === 255 && (data[1] === 251 || data[1] === 250) || data[0] === 73 && data[1] === 68 && data[2] === 51) {
|
|
3343
|
+
return { mime: "audio/mpeg", ext: "mp3" };
|
|
3344
|
+
}
|
|
3345
|
+
if (data[0] === 82 && data[1] === 73 && data[2] === 70 && data[3] === 70 && data[8] === 87 && data[9] === 65 && data[10] === 86 && data[11] === 69) {
|
|
3346
|
+
return { mime: "audio/wav", ext: "wav" };
|
|
3347
|
+
}
|
|
3348
|
+
if (data[0] === 79 && data[1] === 103 && data[2] === 103 && data[3] === 83) {
|
|
3349
|
+
return { mime: "audio/ogg", ext: "ogg" };
|
|
3350
|
+
}
|
|
3351
|
+
if (data[4] === 102 && data[5] === 116 && data[6] === 121 && data[7] === 112 && data[8] === 77 && data[9] === 52 && data[10] === 65) {
|
|
3352
|
+
return { mime: "audio/mp4", ext: "m4a" };
|
|
3353
|
+
}
|
|
3354
|
+
if (data[4] === 102 && data[5] === 116 && data[6] === 121 && data[7] === 112) {
|
|
3355
|
+
return { mime: "video/mp4", ext: "mp4" };
|
|
3356
|
+
}
|
|
3357
|
+
if (data[0] === 26 && data[1] === 69 && data[2] === 223 && data[3] === 163) {
|
|
3358
|
+
return { mime: "video/webm", ext: "webm" };
|
|
3359
|
+
}
|
|
3360
|
+
return void 0;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
// packages/tiny-stdio-mcp-server/src/content/image.ts
|
|
3364
|
+
var SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set([
|
|
3365
|
+
"image/png",
|
|
3366
|
+
"image/jpeg",
|
|
3367
|
+
"image/gif",
|
|
3368
|
+
"image/webp"
|
|
3369
|
+
]);
|
|
3370
|
+
var Image = class _Image {
|
|
3371
|
+
constructor(base64Data, mimeType) {
|
|
3372
|
+
this.base64Data = base64Data;
|
|
3373
|
+
this.mimeType = mimeType;
|
|
3374
|
+
}
|
|
3375
|
+
static async fromUrl(url) {
|
|
3376
|
+
const response = await fetch(url);
|
|
3377
|
+
if (!response.ok) {
|
|
3378
|
+
throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
|
|
3379
|
+
}
|
|
3380
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3381
|
+
const data = new Uint8Array(arrayBuffer);
|
|
3382
|
+
const detected = fileTypeFromBuffer(data);
|
|
3383
|
+
let mimeType;
|
|
3384
|
+
if (detected && SUPPORTED_IMAGE_MIMES.has(detected.mime)) {
|
|
3385
|
+
mimeType = detected.mime;
|
|
3386
|
+
} else {
|
|
3387
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
3388
|
+
if (contentType && SUPPORTED_IMAGE_MIMES.has(contentType)) {
|
|
3389
|
+
mimeType = contentType;
|
|
3390
|
+
} else {
|
|
3391
|
+
throw new Error(`Unable to detect image MIME type from ${url}`);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
3395
|
+
return new _Image(base64, mimeType);
|
|
3396
|
+
}
|
|
3397
|
+
static fromBytes(data, format) {
|
|
3398
|
+
let mimeType;
|
|
3399
|
+
if (format) {
|
|
3400
|
+
mimeType = format.includes("/") ? format : `image/${format}`;
|
|
3401
|
+
} else {
|
|
3402
|
+
const detected = fileTypeFromBuffer(data);
|
|
3403
|
+
if (!detected || !SUPPORTED_IMAGE_MIMES.has(detected.mime)) {
|
|
3404
|
+
throw new Error("Unable to detect image MIME type from bytes");
|
|
3405
|
+
}
|
|
3406
|
+
mimeType = detected.mime;
|
|
3407
|
+
}
|
|
3408
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
3409
|
+
return new _Image(base64, mimeType);
|
|
3410
|
+
}
|
|
3411
|
+
static fromBase64(base64, mimeType) {
|
|
3412
|
+
return new _Image(base64, mimeType);
|
|
3413
|
+
}
|
|
3414
|
+
toContentBlock() {
|
|
3415
|
+
return {
|
|
3416
|
+
type: "image",
|
|
3417
|
+
data: this.base64Data,
|
|
3418
|
+
mimeType: this.mimeType
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
|
|
3423
|
+
// packages/tiny-stdio-mcp-server/src/content/audio.ts
|
|
3424
|
+
var SUPPORTED_AUDIO_MIMES = /* @__PURE__ */ new Set([
|
|
3425
|
+
"audio/mpeg",
|
|
3426
|
+
"audio/wav",
|
|
3427
|
+
"audio/ogg",
|
|
3428
|
+
"audio/mp4"
|
|
3429
|
+
]);
|
|
3430
|
+
var AUDIO_FORMAT_MAP = {
|
|
3431
|
+
mp3: "audio/mpeg",
|
|
3432
|
+
wav: "audio/wav",
|
|
3433
|
+
ogg: "audio/ogg",
|
|
3434
|
+
m4a: "audio/mp4",
|
|
3435
|
+
mpeg: "audio/mpeg"
|
|
3436
|
+
};
|
|
3437
|
+
var Audio = class _Audio {
|
|
3438
|
+
constructor(base64Data, mimeType) {
|
|
3439
|
+
this.base64Data = base64Data;
|
|
3440
|
+
this.mimeType = mimeType;
|
|
3441
|
+
}
|
|
3442
|
+
static async fromUrl(url) {
|
|
3443
|
+
const response = await fetch(url);
|
|
3444
|
+
if (!response.ok) {
|
|
3445
|
+
throw new Error(`Failed to fetch audio from ${url}: ${response.status} ${response.statusText}`);
|
|
3446
|
+
}
|
|
3447
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3448
|
+
const data = new Uint8Array(arrayBuffer);
|
|
3449
|
+
const detected = fileTypeFromBuffer(data);
|
|
3450
|
+
let mimeType;
|
|
3451
|
+
if (detected && SUPPORTED_AUDIO_MIMES.has(detected.mime)) {
|
|
3452
|
+
mimeType = detected.mime;
|
|
3453
|
+
} else {
|
|
3454
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
3455
|
+
if (contentType && SUPPORTED_AUDIO_MIMES.has(contentType)) {
|
|
3456
|
+
mimeType = contentType;
|
|
3457
|
+
} else {
|
|
3458
|
+
throw new Error(`Unable to detect audio MIME type from ${url}`);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
3462
|
+
return new _Audio(base64, mimeType);
|
|
3463
|
+
}
|
|
3464
|
+
static fromBytes(data, format) {
|
|
3465
|
+
let mimeType;
|
|
3466
|
+
if (format) {
|
|
3467
|
+
if (format.includes("/")) {
|
|
3468
|
+
mimeType = format;
|
|
3469
|
+
} else {
|
|
3470
|
+
mimeType = AUDIO_FORMAT_MAP[format.toLowerCase()] || `audio/${format}`;
|
|
3471
|
+
}
|
|
3472
|
+
} else {
|
|
3473
|
+
const detected = fileTypeFromBuffer(data);
|
|
3474
|
+
if (!detected || !SUPPORTED_AUDIO_MIMES.has(detected.mime)) {
|
|
3475
|
+
throw new Error("Unable to detect audio MIME type from bytes");
|
|
3476
|
+
}
|
|
3477
|
+
mimeType = detected.mime;
|
|
3478
|
+
}
|
|
3479
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
3480
|
+
return new _Audio(base64, mimeType);
|
|
3481
|
+
}
|
|
3482
|
+
static fromBase64(base64, mimeType) {
|
|
3483
|
+
return new _Audio(base64, mimeType);
|
|
3484
|
+
}
|
|
3485
|
+
toContentBlock() {
|
|
3486
|
+
return {
|
|
3487
|
+
type: "audio",
|
|
3488
|
+
data: this.base64Data,
|
|
3489
|
+
mimeType: this.mimeType
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
};
|
|
3493
|
+
|
|
3494
|
+
// packages/tiny-stdio-mcp-server/src/content/file.ts
|
|
3495
|
+
function isTextMimeType(mimeType) {
|
|
3496
|
+
return mimeType.startsWith("text/") || mimeType === "application/json" || mimeType === "application/xml" || mimeType === "application/javascript" || mimeType === "application/typescript";
|
|
3497
|
+
}
|
|
3498
|
+
var File = class _File {
|
|
3499
|
+
constructor(data, mimeType, isText, name) {
|
|
3500
|
+
this.data = data;
|
|
3501
|
+
this.mimeType = mimeType;
|
|
3502
|
+
this.isText = isText;
|
|
3503
|
+
this.name = name;
|
|
3504
|
+
}
|
|
3505
|
+
static async fromUrl(url) {
|
|
3506
|
+
const response = await fetch(url);
|
|
3507
|
+
if (!response.ok) {
|
|
3508
|
+
throw new Error(`Failed to fetch file from ${url}: ${response.status} ${response.statusText}`);
|
|
3509
|
+
}
|
|
3510
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3511
|
+
const data = new Uint8Array(arrayBuffer);
|
|
3512
|
+
const detected = fileTypeFromBuffer(data);
|
|
3513
|
+
let mimeType;
|
|
3514
|
+
if (detected) {
|
|
3515
|
+
mimeType = detected.mime;
|
|
3516
|
+
} else {
|
|
3517
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
3518
|
+
if (contentType) {
|
|
3519
|
+
mimeType = contentType;
|
|
3520
|
+
} else {
|
|
3521
|
+
throw new Error(`Unable to detect MIME type from ${url}`);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
const isText = isTextMimeType(mimeType);
|
|
3525
|
+
const name = url.split("/").pop() || "file";
|
|
3526
|
+
return new _File(data, mimeType, isText, name);
|
|
3527
|
+
}
|
|
3528
|
+
static fromBytes(data, mimeType) {
|
|
3529
|
+
const isText = isTextMimeType(mimeType);
|
|
3530
|
+
return new _File(data, mimeType, isText);
|
|
3531
|
+
}
|
|
3532
|
+
static fromText(text, mimeType = "text/plain") {
|
|
3533
|
+
return new _File(text, mimeType, true);
|
|
3534
|
+
}
|
|
3535
|
+
static fromBase64(base64, mimeType) {
|
|
3536
|
+
const data = Buffer.from(base64, "base64");
|
|
3537
|
+
const isText = isTextMimeType(mimeType);
|
|
3538
|
+
return new _File(new Uint8Array(data), mimeType, isText);
|
|
3539
|
+
}
|
|
3540
|
+
toContentBlock() {
|
|
3541
|
+
const uri = this.name ? `file:///${this.name}` : "file:///data";
|
|
3542
|
+
if (this.isText) {
|
|
3543
|
+
let text;
|
|
3544
|
+
if (typeof this.data === "string") {
|
|
3545
|
+
text = this.data;
|
|
3546
|
+
} else {
|
|
3547
|
+
text = new TextDecoder("utf-8").decode(this.data);
|
|
3548
|
+
}
|
|
3549
|
+
return {
|
|
3550
|
+
type: "resource",
|
|
3551
|
+
resource: {
|
|
3552
|
+
uri,
|
|
3553
|
+
mimeType: this.mimeType,
|
|
3554
|
+
text
|
|
3555
|
+
}
|
|
3556
|
+
};
|
|
3557
|
+
} else {
|
|
3558
|
+
let blob;
|
|
3559
|
+
if (typeof this.data === "string") {
|
|
3560
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
3561
|
+
} else {
|
|
3562
|
+
blob = Buffer.from(this.data).toString("base64");
|
|
3563
|
+
}
|
|
3564
|
+
return {
|
|
3565
|
+
type: "resource",
|
|
3566
|
+
resource: {
|
|
3567
|
+
uri,
|
|
3568
|
+
mimeType: this.mimeType,
|
|
3569
|
+
blob
|
|
3570
|
+
}
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
|
|
3576
|
+
// packages/tiny-stdio-mcp-server/src/content/convert.ts
|
|
3577
|
+
function convertSingleValue(value) {
|
|
3578
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
3579
|
+
return { type: "text", text: String(value) };
|
|
3580
|
+
}
|
|
3581
|
+
if (value === null) {
|
|
3582
|
+
return { type: "text", text: "null" };
|
|
3583
|
+
}
|
|
3584
|
+
if (value instanceof Image) {
|
|
3585
|
+
return value.toContentBlock();
|
|
3586
|
+
}
|
|
3587
|
+
if (value instanceof Audio) {
|
|
3588
|
+
return value.toContentBlock();
|
|
3589
|
+
}
|
|
3590
|
+
if (value instanceof File) {
|
|
3591
|
+
return value.toContentBlock();
|
|
3592
|
+
}
|
|
3593
|
+
if (isContentBlock(value)) {
|
|
3594
|
+
return value;
|
|
3595
|
+
}
|
|
3596
|
+
return { type: "text", text: JSON.stringify(value) };
|
|
3597
|
+
}
|
|
3598
|
+
function toContentBlocks(result) {
|
|
3599
|
+
if (result === void 0) {
|
|
3600
|
+
return [];
|
|
3601
|
+
}
|
|
3602
|
+
if (Array.isArray(result)) {
|
|
3603
|
+
return result.flatMap((item) => toContentBlocks(item));
|
|
3604
|
+
}
|
|
3605
|
+
return [convertSingleValue(result)];
|
|
3606
|
+
}
|
|
3607
|
+
function isContentBlock(value) {
|
|
3608
|
+
if (!("type" in value) || typeof value.type !== "string") {
|
|
3609
|
+
return false;
|
|
3610
|
+
}
|
|
3611
|
+
return value.type === "text" || value.type === "image" || value.type === "audio" || value.type === "resource";
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// packages/tiny-stdio-mcp-server/src/server.ts
|
|
3615
|
+
var PROTOCOL_VERSION = "2025-11-25";
|
|
3616
|
+
function createServer(options) {
|
|
3617
|
+
const tools = /* @__PURE__ */ new Map();
|
|
3618
|
+
const notificationListeners = /* @__PURE__ */ new Set();
|
|
3619
|
+
let initialized = false;
|
|
3620
|
+
const handleMessage = async (method, params) => {
|
|
3621
|
+
if (method === "ping") {
|
|
3622
|
+
return { result: {} };
|
|
3623
|
+
}
|
|
3624
|
+
if (method === "initialize") {
|
|
3625
|
+
initialized = true;
|
|
3626
|
+
const requestedProtocol = typeof params?.protocolVersion === "string" ? params.protocolVersion : null;
|
|
3627
|
+
const result = {
|
|
3628
|
+
protocolVersion: requestedProtocol ?? PROTOCOL_VERSION,
|
|
3629
|
+
capabilities: {
|
|
3630
|
+
tools: {
|
|
3631
|
+
listChanged: true
|
|
3632
|
+
}
|
|
3633
|
+
},
|
|
3634
|
+
serverInfo: {
|
|
3635
|
+
name: options.name,
|
|
3636
|
+
version: options.version
|
|
3637
|
+
}
|
|
3638
|
+
};
|
|
3639
|
+
return { result };
|
|
3640
|
+
}
|
|
3641
|
+
if (method === "notifications/initialized") {
|
|
3642
|
+
return { result: void 0 };
|
|
3643
|
+
}
|
|
3644
|
+
if (!initialized) {
|
|
3645
|
+
return {
|
|
3646
|
+
error: {
|
|
3647
|
+
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
3648
|
+
message: "Server not initialized"
|
|
3649
|
+
}
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
if (method === "tools/list") {
|
|
3653
|
+
const toolList = [];
|
|
3654
|
+
for (const tool of tools.values()) {
|
|
3655
|
+
toolList.push({
|
|
3656
|
+
name: tool.name,
|
|
3657
|
+
description: tool.description,
|
|
3658
|
+
inputSchema: tool.inputSchema
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
return { result: { tools: toolList } };
|
|
3662
|
+
}
|
|
3663
|
+
if (method === "tools/call") {
|
|
3664
|
+
const toolName = params?.name;
|
|
3665
|
+
const toolArgs = params?.arguments || {};
|
|
3666
|
+
if (!toolName) {
|
|
3667
|
+
return {
|
|
3668
|
+
error: {
|
|
3669
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
3670
|
+
message: "Tool name required"
|
|
3671
|
+
}
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
const tool = tools.get(toolName);
|
|
3675
|
+
if (!tool) {
|
|
3676
|
+
return {
|
|
3677
|
+
error: {
|
|
3678
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
3679
|
+
message: `Tool not found: ${toolName}`
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
try {
|
|
3684
|
+
const handlerResult = await tool.handler(toolArgs);
|
|
3685
|
+
const result = { content: toContentBlocks(handlerResult) };
|
|
3686
|
+
return { result };
|
|
3687
|
+
} catch (err) {
|
|
3688
|
+
if (err instanceof ToolError) {
|
|
3689
|
+
return {
|
|
3690
|
+
error: {
|
|
3691
|
+
code: err.code,
|
|
3692
|
+
message: err.message
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3697
|
+
const result = {
|
|
3698
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
3699
|
+
isError: true
|
|
3700
|
+
};
|
|
3701
|
+
return { result };
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
return {
|
|
3705
|
+
error: {
|
|
3706
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
3707
|
+
message: "Method not found"
|
|
3708
|
+
}
|
|
3709
|
+
};
|
|
3710
|
+
};
|
|
3711
|
+
const processLine = async (line, write2) => {
|
|
3712
|
+
const parsed = parseMessage(line);
|
|
3713
|
+
if (!parsed.success) {
|
|
3714
|
+
write2(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
const { request, isNotification } = parsed;
|
|
3718
|
+
const { result, error } = await server.handleMessage(
|
|
3719
|
+
request.method,
|
|
3720
|
+
request.params
|
|
3721
|
+
);
|
|
3722
|
+
if (isNotification) {
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
const requestWithId = request;
|
|
3726
|
+
if (error) {
|
|
3727
|
+
write2(formatErrorResponse(requestWithId.id, error) + "\n");
|
|
3728
|
+
} else if (result !== void 0) {
|
|
3729
|
+
write2(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
3730
|
+
}
|
|
3731
|
+
};
|
|
3732
|
+
const broadcastNotification = (method) => {
|
|
3733
|
+
const notification = {
|
|
3734
|
+
jsonrpc: "2.0",
|
|
3735
|
+
method
|
|
3736
|
+
};
|
|
3737
|
+
for (const listener of notificationListeners) {
|
|
3738
|
+
listener(notification);
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
const server = {
|
|
3742
|
+
tool(name, description, inputSchema, handler) {
|
|
3743
|
+
tools.set(name, {
|
|
3744
|
+
name,
|
|
3745
|
+
description,
|
|
3746
|
+
inputSchema,
|
|
3747
|
+
handler
|
|
3748
|
+
});
|
|
3749
|
+
return server;
|
|
3750
|
+
},
|
|
3751
|
+
onNotification(listener) {
|
|
3752
|
+
notificationListeners.add(listener);
|
|
3753
|
+
return () => {
|
|
3754
|
+
notificationListeners.delete(listener);
|
|
3755
|
+
};
|
|
3756
|
+
},
|
|
3757
|
+
removeTool(name) {
|
|
3758
|
+
return tools.delete(name);
|
|
3759
|
+
},
|
|
3760
|
+
async notifyToolsChanged() {
|
|
3761
|
+
if (initialized) {
|
|
3762
|
+
broadcastNotification("notifications/tools/list_changed");
|
|
3763
|
+
}
|
|
3764
|
+
},
|
|
3765
|
+
handleMessage,
|
|
3766
|
+
async listen() {
|
|
3767
|
+
return server.connect({
|
|
3768
|
+
readable: process.stdin,
|
|
3769
|
+
writable: process.stdout
|
|
3770
|
+
});
|
|
3771
|
+
},
|
|
3772
|
+
async connect(transport) {
|
|
3773
|
+
return new Promise((resolve2) => {
|
|
3774
|
+
const unsubscribe = server.onNotification((notification) => {
|
|
3775
|
+
transport.writable.write(`${JSON.stringify(notification)}
|
|
3776
|
+
`);
|
|
3777
|
+
});
|
|
3778
|
+
const rl = readline.createInterface({
|
|
3779
|
+
input: transport.readable,
|
|
3780
|
+
crlfDelay: Infinity
|
|
3781
|
+
});
|
|
3782
|
+
rl.on("line", (line) => {
|
|
3783
|
+
processLine(line, (data) => transport.writable.write(data));
|
|
3784
|
+
});
|
|
3785
|
+
rl.on("close", () => {
|
|
3786
|
+
unsubscribe();
|
|
3787
|
+
resolve2();
|
|
3788
|
+
});
|
|
3789
|
+
});
|
|
3790
|
+
},
|
|
3791
|
+
async connectSDK(transport) {
|
|
3792
|
+
return new Promise((resolve2) => {
|
|
3793
|
+
const unsubscribe = server.onNotification((notification) => {
|
|
3794
|
+
void transport.send(notification);
|
|
3795
|
+
});
|
|
3796
|
+
transport.onmessage = async (message) => {
|
|
3797
|
+
if (!("method" in message)) {
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
if (!("id" in message) || message.id === void 0) {
|
|
3801
|
+
await server.handleMessage(message.method, message.params);
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
const request = message;
|
|
3805
|
+
const { result, error } = await server.handleMessage(
|
|
3806
|
+
request.method,
|
|
3807
|
+
request.params
|
|
3808
|
+
);
|
|
3809
|
+
if (error) {
|
|
3810
|
+
const response = {
|
|
3811
|
+
jsonrpc: "2.0",
|
|
3812
|
+
id: request.id,
|
|
3813
|
+
error
|
|
3814
|
+
};
|
|
3815
|
+
await transport.send(response);
|
|
3816
|
+
} else if (result !== void 0) {
|
|
3817
|
+
const response = {
|
|
3818
|
+
jsonrpc: "2.0",
|
|
3819
|
+
id: request.id,
|
|
3820
|
+
result
|
|
3821
|
+
};
|
|
3822
|
+
await transport.send(response);
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
transport.onclose = () => {
|
|
3826
|
+
unsubscribe();
|
|
3827
|
+
resolve2();
|
|
3828
|
+
};
|
|
3829
|
+
transport.start();
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
return server;
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
// packages/tiny-stdio-mcp-server/src/schema.ts
|
|
3837
|
+
function defineSchema(definition) {
|
|
3838
|
+
const properties = {};
|
|
3839
|
+
const required = [];
|
|
3840
|
+
for (const [key, prop] of Object.entries(definition)) {
|
|
3841
|
+
properties[key] = {
|
|
3842
|
+
type: prop.type,
|
|
3843
|
+
...prop.description !== void 0 && { description: prop.description }
|
|
3844
|
+
};
|
|
3845
|
+
if (!prop.optional) {
|
|
3846
|
+
required.push(key);
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
return {
|
|
3850
|
+
type: "object",
|
|
3851
|
+
properties,
|
|
3852
|
+
required
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
// packages/memory/src/mcp.ts
|
|
3857
|
+
async function startMemoryMcpServer(opts) {
|
|
3858
|
+
const server = createServer({
|
|
3859
|
+
name: "poe-code-memory",
|
|
3860
|
+
version: "0.0.1"
|
|
3861
|
+
});
|
|
3862
|
+
server.tool("list_pages", "List memory pages.", defineSchema({}), async () => ({
|
|
3863
|
+
pages: (await listPages(opts.root)).map((page) => ({
|
|
3864
|
+
rel_path: page.relPath,
|
|
3865
|
+
description: page.frontmatter.description ?? ""
|
|
3866
|
+
}))
|
|
3867
|
+
}));
|
|
3868
|
+
server.tool(
|
|
3869
|
+
"read_page",
|
|
3870
|
+
"Read a memory page.",
|
|
3871
|
+
defineSchema({ rel_path: { type: "string" } }),
|
|
3872
|
+
async ({ rel_path }) => {
|
|
3873
|
+
const page = await readPage(opts.root, rel_path);
|
|
3874
|
+
return {
|
|
3875
|
+
rel_path: page.relPath,
|
|
3876
|
+
frontmatter: page.frontmatter,
|
|
3877
|
+
body: page.body,
|
|
3878
|
+
bytes: page.bytes
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
);
|
|
3882
|
+
server.tool(
|
|
3883
|
+
"search_memory",
|
|
3884
|
+
"Search memory pages.",
|
|
3885
|
+
defineSchema({
|
|
3886
|
+
query: { type: "string" },
|
|
3887
|
+
limit: { type: "number", optional: true }
|
|
3888
|
+
}),
|
|
3889
|
+
async ({ query, limit }) => {
|
|
3890
|
+
const hits = await searchMemory(opts.root, query);
|
|
3891
|
+
return { hits: typeof limit === "number" ? hits.slice(0, limit) : hits };
|
|
3892
|
+
}
|
|
3893
|
+
);
|
|
3894
|
+
server.tool("status", "Show memory status.", defineSchema({}), async () => statusOf(opts.root));
|
|
3895
|
+
if (opts.allowWrites) {
|
|
3896
|
+
server.tool(
|
|
3897
|
+
"append_to_page",
|
|
3898
|
+
"Append content to a memory page.",
|
|
3899
|
+
defineSchema({
|
|
3900
|
+
rel_path: { type: "string" },
|
|
3901
|
+
content: { type: "string" },
|
|
3902
|
+
reason: { type: "string" }
|
|
3903
|
+
}),
|
|
3904
|
+
async ({ rel_path, content, reason }) => ({
|
|
3905
|
+
diff: await appendToPage(opts.root, rel_path, content, { reason })
|
|
3906
|
+
})
|
|
3907
|
+
);
|
|
3908
|
+
}
|
|
3909
|
+
return {
|
|
3910
|
+
server,
|
|
3911
|
+
stop: async () => {
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
function printMcpConfig() {
|
|
3916
|
+
return JSON.stringify(
|
|
3917
|
+
{
|
|
3918
|
+
mcpServers: {
|
|
3919
|
+
"poe-code-memory": {
|
|
3920
|
+
type: "stdio",
|
|
3921
|
+
command: "poe-code",
|
|
3922
|
+
args: ["memory-mcp"]
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
},
|
|
3926
|
+
null,
|
|
3927
|
+
2
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
// packages/agent-skill-config/src/configs.ts
|
|
3932
|
+
import os from "node:os";
|
|
3933
|
+
import path21 from "node:path";
|
|
3934
|
+
|
|
3935
|
+
// packages/agent-defs/src/agents/claude-code.ts
|
|
3936
|
+
var claudeCodeAgent = {
|
|
3937
|
+
id: "claude-code",
|
|
3938
|
+
name: "claude-code",
|
|
3939
|
+
label: "Claude Code",
|
|
3940
|
+
summary: "Configure Claude Code to route through Poe.",
|
|
3941
|
+
aliases: ["claude"],
|
|
3942
|
+
binaryName: "claude",
|
|
3943
|
+
configPath: "~/.claude/settings.json",
|
|
3944
|
+
branding: {
|
|
3945
|
+
colors: {
|
|
3946
|
+
dark: "#C15F3C",
|
|
3947
|
+
light: "#C15F3C"
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
};
|
|
3951
|
+
|
|
3952
|
+
// packages/agent-defs/src/agents/claude-desktop.ts
|
|
3953
|
+
var claudeDesktopAgent = {
|
|
3954
|
+
id: "claude-desktop",
|
|
3955
|
+
name: "claude-desktop",
|
|
3956
|
+
label: "Claude Desktop",
|
|
3957
|
+
summary: "Anthropic's official desktop application for Claude",
|
|
3958
|
+
configPath: "~/.claude/settings.json",
|
|
3959
|
+
branding: {
|
|
3960
|
+
colors: {
|
|
3961
|
+
dark: "#D97757",
|
|
3962
|
+
light: "#D97757"
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
|
|
3967
|
+
// packages/agent-defs/src/agents/codex.ts
|
|
3968
|
+
var codexAgent = {
|
|
3969
|
+
id: "codex",
|
|
3970
|
+
name: "codex",
|
|
3971
|
+
label: "Codex",
|
|
3972
|
+
summary: "Configure Codex to use Poe as the model provider.",
|
|
3973
|
+
binaryName: "codex",
|
|
3974
|
+
configPath: "~/.codex/config.toml",
|
|
3975
|
+
branding: {
|
|
3976
|
+
colors: {
|
|
3977
|
+
dark: "#D5D9DF",
|
|
3978
|
+
light: "#7A7F86"
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
// packages/agent-defs/src/agents/opencode.ts
|
|
3984
|
+
var openCodeAgent = {
|
|
3985
|
+
id: "opencode",
|
|
3986
|
+
name: "opencode",
|
|
3987
|
+
label: "OpenCode CLI",
|
|
3988
|
+
summary: "Configure OpenCode CLI to use the Poe API.",
|
|
3989
|
+
binaryName: "opencode",
|
|
3990
|
+
configPath: "~/.config/opencode/config.json",
|
|
3991
|
+
branding: {
|
|
3992
|
+
colors: {
|
|
3993
|
+
dark: "#4A4F55",
|
|
3994
|
+
light: "#2F3338"
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
|
|
3999
|
+
// packages/agent-defs/src/agents/kimi.ts
|
|
4000
|
+
var kimiAgent = {
|
|
4001
|
+
id: "kimi",
|
|
4002
|
+
name: "kimi",
|
|
4003
|
+
label: "Kimi",
|
|
4004
|
+
summary: "Configure Kimi CLI to use Poe API",
|
|
4005
|
+
aliases: ["kimi-cli"],
|
|
4006
|
+
binaryName: "kimi",
|
|
4007
|
+
configPath: "~/.kimi/config.toml",
|
|
4008
|
+
branding: {
|
|
4009
|
+
colors: {
|
|
4010
|
+
dark: "#7B68EE",
|
|
4011
|
+
light: "#6A5ACD"
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
|
|
4016
|
+
// packages/agent-defs/src/agents/goose.ts
|
|
4017
|
+
var gooseAgent = {
|
|
4018
|
+
id: "goose",
|
|
4019
|
+
name: "goose",
|
|
4020
|
+
label: "Goose",
|
|
4021
|
+
summary: "Block's open-source AI agent with ACP support.",
|
|
4022
|
+
binaryName: "goose",
|
|
4023
|
+
configPath: "~/.config/goose/config.yaml",
|
|
4024
|
+
branding: {
|
|
4025
|
+
colors: {
|
|
4026
|
+
dark: "#FF6B35",
|
|
4027
|
+
light: "#E85D26"
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
|
|
4032
|
+
// packages/agent-defs/src/agents/poe-agent.ts
|
|
4033
|
+
var poeAgentAgent = {
|
|
4034
|
+
id: "poe-agent",
|
|
4035
|
+
name: "poe-agent",
|
|
4036
|
+
label: "Poe Agent",
|
|
4037
|
+
summary: "Run one-shot prompts with the built-in Poe agent runtime.",
|
|
4038
|
+
configPath: "~/.poe-code/config.json",
|
|
4039
|
+
branding: {
|
|
4040
|
+
colors: {
|
|
4041
|
+
dark: "#A465F7",
|
|
4042
|
+
light: "#7A3FD3"
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
};
|
|
4046
|
+
|
|
4047
|
+
// packages/agent-defs/src/registry.ts
|
|
4048
|
+
var allAgents = [
|
|
4049
|
+
claudeCodeAgent,
|
|
4050
|
+
claudeDesktopAgent,
|
|
4051
|
+
codexAgent,
|
|
4052
|
+
openCodeAgent,
|
|
4053
|
+
kimiAgent,
|
|
4054
|
+
gooseAgent,
|
|
4055
|
+
poeAgentAgent
|
|
4056
|
+
];
|
|
4057
|
+
var lookup = /* @__PURE__ */ new Map();
|
|
4058
|
+
for (const agent of allAgents) {
|
|
4059
|
+
const values = [agent.id, agent.name, ...agent.aliases ?? []];
|
|
4060
|
+
for (const value of values) {
|
|
4061
|
+
const normalized = value.toLowerCase();
|
|
4062
|
+
if (!lookup.has(normalized)) {
|
|
4063
|
+
lookup.set(normalized, agent.id);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
function resolveAgentId(input) {
|
|
4068
|
+
if (!input) {
|
|
4069
|
+
return void 0;
|
|
4070
|
+
}
|
|
4071
|
+
return lookup.get(input.toLowerCase());
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
// packages/agent-skill-config/src/configs.ts
|
|
4075
|
+
var agentSkillConfigs = {
|
|
4076
|
+
"claude-code": {
|
|
4077
|
+
globalSkillDir: "~/.claude/skills",
|
|
4078
|
+
localSkillDir: ".claude/skills"
|
|
4079
|
+
},
|
|
4080
|
+
codex: {
|
|
4081
|
+
globalSkillDir: "~/.codex/skills",
|
|
4082
|
+
localSkillDir: ".codex/skills"
|
|
4083
|
+
},
|
|
4084
|
+
opencode: {
|
|
4085
|
+
globalSkillDir: "~/.config/opencode/skills",
|
|
4086
|
+
localSkillDir: ".opencode/skills"
|
|
4087
|
+
},
|
|
4088
|
+
goose: {
|
|
4089
|
+
globalSkillDir: "~/.agents/skills",
|
|
4090
|
+
localSkillDir: ".agents/skills"
|
|
4091
|
+
}
|
|
4092
|
+
};
|
|
4093
|
+
var supportedAgents = Object.keys(agentSkillConfigs);
|
|
4094
|
+
function resolveAgentSupport(input, registry = agentSkillConfigs) {
|
|
4095
|
+
const resolvedId = resolveAgentId(input);
|
|
4096
|
+
if (!resolvedId) {
|
|
4097
|
+
return { status: "unknown", input };
|
|
4098
|
+
}
|
|
4099
|
+
const config = registry[resolvedId];
|
|
4100
|
+
if (!config) {
|
|
4101
|
+
return { status: "unsupported", input, id: resolvedId };
|
|
4102
|
+
}
|
|
4103
|
+
return { status: "supported", input, id: resolvedId, config };
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// packages/agent-skill-config/src/templates.ts
|
|
4107
|
+
import { readFile as readFile11, stat as stat5 } from "node:fs/promises";
|
|
4108
|
+
import path22 from "node:path";
|
|
4109
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4110
|
+
|
|
4111
|
+
// packages/agent-skill-config/src/apply.ts
|
|
4112
|
+
var UnsupportedAgentError = class extends Error {
|
|
4113
|
+
constructor(agentId) {
|
|
4114
|
+
super(`Unsupported agent: ${agentId}`);
|
|
4115
|
+
this.name = "UnsupportedAgentError";
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
function toHomeRelative(localSkillDir) {
|
|
4119
|
+
if (localSkillDir.startsWith("~/") || localSkillDir === "~") {
|
|
4120
|
+
return localSkillDir;
|
|
4121
|
+
}
|
|
4122
|
+
const normalized = localSkillDir.startsWith("./") ? localSkillDir.slice(2) : localSkillDir;
|
|
4123
|
+
return `~/${normalized}`;
|
|
4124
|
+
}
|
|
4125
|
+
var SKILL_TEMPLATE_ID = "__skill_content__";
|
|
4126
|
+
async function installSkill(agentId, skill, options) {
|
|
4127
|
+
const support = resolveAgentSupport(agentId);
|
|
4128
|
+
if (support.status !== "supported") {
|
|
4129
|
+
throw new UnsupportedAgentError(agentId);
|
|
4130
|
+
}
|
|
4131
|
+
const scope = options.scope ?? "local";
|
|
4132
|
+
const config = support.config;
|
|
4133
|
+
const skillDir = scope === "global" ? config.globalSkillDir : toHomeRelative(config.localSkillDir);
|
|
4134
|
+
const skillFolderPath = `${skillDir}/${skill.name}`;
|
|
4135
|
+
const skillFilePath = `${skillFolderPath}/SKILL.md`;
|
|
4136
|
+
const displayPath = `${scope === "global" ? config.globalSkillDir : config.localSkillDir}/${skill.name}/SKILL.md`;
|
|
4137
|
+
await runMutations(
|
|
4138
|
+
[
|
|
4139
|
+
fileMutation.ensureDirectory({
|
|
4140
|
+
path: skillFolderPath,
|
|
4141
|
+
label: `Ensure skill directory ${skill.name}`
|
|
4142
|
+
}),
|
|
4143
|
+
templateMutation.write({
|
|
4144
|
+
target: skillFilePath,
|
|
4145
|
+
templateId: SKILL_TEMPLATE_ID,
|
|
4146
|
+
label: `Write skill ${skill.name}`
|
|
4147
|
+
})
|
|
4148
|
+
],
|
|
4149
|
+
{
|
|
4150
|
+
fs: options.fs,
|
|
4151
|
+
homeDir: scope === "global" ? options.homeDir : options.cwd,
|
|
4152
|
+
dryRun: options.dryRun,
|
|
4153
|
+
observers: options.observers,
|
|
4154
|
+
templates: async (templateId) => {
|
|
4155
|
+
if (templateId === SKILL_TEMPLATE_ID) {
|
|
4156
|
+
return skill.content;
|
|
4157
|
+
}
|
|
4158
|
+
throw new Error(`Unknown template: ${templateId}`);
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
);
|
|
4162
|
+
return { skillPath: skillFilePath, displayPath };
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
// packages/agent-mcp-config/src/configs.ts
|
|
4166
|
+
var agentMcpConfigs = {
|
|
4167
|
+
"claude-code": {
|
|
4168
|
+
configFile: "~/.claude.json",
|
|
4169
|
+
configKey: "mcpServers",
|
|
4170
|
+
format: "json",
|
|
4171
|
+
shape: "standard"
|
|
4172
|
+
},
|
|
4173
|
+
"claude-desktop": {
|
|
4174
|
+
configFile: (platform) => {
|
|
4175
|
+
switch (platform) {
|
|
4176
|
+
case "darwin":
|
|
4177
|
+
return "~/Library/Application Support/Claude/claude_desktop_config.json";
|
|
4178
|
+
case "win32":
|
|
4179
|
+
return "~/AppData/Roaming/Claude/claude_desktop_config.json";
|
|
4180
|
+
default:
|
|
4181
|
+
return "~/.config/Claude/claude_desktop_config.json";
|
|
4182
|
+
}
|
|
4183
|
+
},
|
|
4184
|
+
configKey: "mcpServers",
|
|
4185
|
+
format: "json",
|
|
4186
|
+
shape: "standard",
|
|
4187
|
+
mcpOutputFormat: "markdown_instructions"
|
|
4188
|
+
},
|
|
4189
|
+
codex: {
|
|
4190
|
+
configFile: "~/.codex/config.toml",
|
|
4191
|
+
configKey: "mcp_servers",
|
|
4192
|
+
format: "toml",
|
|
4193
|
+
shape: "standard"
|
|
4194
|
+
},
|
|
4195
|
+
opencode: {
|
|
4196
|
+
configFile: "~/.config/opencode/opencode.json",
|
|
4197
|
+
configKey: "mcp",
|
|
4198
|
+
format: "json",
|
|
4199
|
+
shape: "opencode"
|
|
4200
|
+
},
|
|
4201
|
+
kimi: {
|
|
4202
|
+
configFile: "~/.kimi/mcp.json",
|
|
4203
|
+
configKey: "mcpServers",
|
|
4204
|
+
format: "json",
|
|
4205
|
+
shape: "standard"
|
|
4206
|
+
},
|
|
4207
|
+
goose: {
|
|
4208
|
+
configFile: "~/.config/goose/config.yaml",
|
|
4209
|
+
configKey: "extensions",
|
|
4210
|
+
format: "yaml",
|
|
4211
|
+
shape: "goose"
|
|
4212
|
+
}
|
|
4213
|
+
};
|
|
4214
|
+
var supportedAgents2 = Object.keys(agentMcpConfigs);
|
|
4215
|
+
function resolveAgentSupport2(input, registry = agentMcpConfigs) {
|
|
4216
|
+
const resolvedId = resolveAgentId(input);
|
|
4217
|
+
if (!resolvedId) {
|
|
4218
|
+
return { status: "unknown", input };
|
|
4219
|
+
}
|
|
4220
|
+
const config = registry[resolvedId];
|
|
4221
|
+
if (!config) {
|
|
4222
|
+
return { status: "unsupported", input, id: resolvedId };
|
|
4223
|
+
}
|
|
4224
|
+
return { status: "supported", input, id: resolvedId, config };
|
|
4225
|
+
}
|
|
4226
|
+
function isSupported(agentId) {
|
|
4227
|
+
return resolveAgentSupport2(agentId).status === "supported";
|
|
4228
|
+
}
|
|
4229
|
+
function getAgentConfig2(agentId) {
|
|
4230
|
+
const support = resolveAgentSupport2(agentId);
|
|
4231
|
+
return support.status === "supported" ? support.config : void 0;
|
|
4232
|
+
}
|
|
4233
|
+
function resolveConfigPath2(config, platform) {
|
|
4234
|
+
if (typeof config.configFile === "function") {
|
|
4235
|
+
return config.configFile(platform);
|
|
4236
|
+
}
|
|
4237
|
+
return config.configFile;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
// packages/agent-mcp-config/src/apply.ts
|
|
4241
|
+
import path23 from "node:path";
|
|
4242
|
+
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
4243
|
+
|
|
4244
|
+
// packages/agent-mcp-config/src/shapes.ts
|
|
4245
|
+
function transformStdioServer(config, enabled) {
|
|
4246
|
+
if (!enabled) {
|
|
4247
|
+
return void 0;
|
|
4248
|
+
}
|
|
4249
|
+
const result = {
|
|
4250
|
+
command: config.command
|
|
4251
|
+
};
|
|
4252
|
+
if (config.args && config.args.length > 0) {
|
|
4253
|
+
result.args = config.args;
|
|
4254
|
+
}
|
|
4255
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
4256
|
+
result.env = config.env;
|
|
4257
|
+
}
|
|
4258
|
+
return result;
|
|
4259
|
+
}
|
|
4260
|
+
function standardShape(entry) {
|
|
4261
|
+
const enabled = entry.enabled !== false;
|
|
4262
|
+
if (entry.config.transport === "stdio") {
|
|
4263
|
+
return transformStdioServer(entry.config, enabled);
|
|
4264
|
+
}
|
|
4265
|
+
if (!enabled) {
|
|
4266
|
+
return void 0;
|
|
4267
|
+
}
|
|
4268
|
+
return {
|
|
4269
|
+
command: entry.config.url
|
|
4270
|
+
};
|
|
4271
|
+
}
|
|
4272
|
+
function transformStdioServerOpencode(config, enabled) {
|
|
4273
|
+
const command = config.args && config.args.length > 0 ? [config.command, ...config.args] : [config.command];
|
|
4274
|
+
const result = {
|
|
4275
|
+
type: "local",
|
|
4276
|
+
command,
|
|
4277
|
+
enabled
|
|
4278
|
+
};
|
|
4279
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
4280
|
+
result.env = config.env;
|
|
4281
|
+
}
|
|
4282
|
+
return result;
|
|
4283
|
+
}
|
|
4284
|
+
function opencodeShape(entry) {
|
|
4285
|
+
const enabled = entry.enabled !== false;
|
|
4286
|
+
if (entry.config.transport === "stdio") {
|
|
4287
|
+
return transformStdioServerOpencode(entry.config, enabled);
|
|
4288
|
+
}
|
|
4289
|
+
return {
|
|
4290
|
+
type: "local",
|
|
4291
|
+
command: [entry.config.url],
|
|
4292
|
+
enabled
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
function gooseShape(entry) {
|
|
4296
|
+
const enabled = entry.enabled !== false;
|
|
4297
|
+
if (!enabled) {
|
|
4298
|
+
return void 0;
|
|
4299
|
+
}
|
|
4300
|
+
if (entry.config.transport === "stdio") {
|
|
4301
|
+
const result2 = {
|
|
4302
|
+
type: "stdio",
|
|
4303
|
+
cmd: entry.config.command
|
|
4304
|
+
};
|
|
4305
|
+
if (entry.config.args && entry.config.args.length > 0) {
|
|
4306
|
+
result2.args = entry.config.args;
|
|
4307
|
+
}
|
|
4308
|
+
if (entry.config.env && Object.keys(entry.config.env).length > 0) {
|
|
4309
|
+
result2.envs = entry.config.env;
|
|
4310
|
+
}
|
|
4311
|
+
return result2;
|
|
4312
|
+
}
|
|
4313
|
+
const result = {
|
|
4314
|
+
type: "http",
|
|
4315
|
+
url: entry.config.url
|
|
4316
|
+
};
|
|
4317
|
+
if (entry.config.headers && Object.keys(entry.config.headers).length > 0) {
|
|
4318
|
+
result.headers = entry.config.headers;
|
|
4319
|
+
}
|
|
4320
|
+
return result;
|
|
4321
|
+
}
|
|
4322
|
+
var shapeTransformers = {
|
|
4323
|
+
standard: standardShape,
|
|
4324
|
+
opencode: opencodeShape,
|
|
4325
|
+
goose: gooseShape
|
|
4326
|
+
};
|
|
4327
|
+
function getShapeTransformer(shape) {
|
|
4328
|
+
return shapeTransformers[shape];
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
// packages/agent-mcp-config/src/apply.ts
|
|
4332
|
+
function getConfigDirectory(configPath) {
|
|
4333
|
+
return path23.dirname(configPath);
|
|
4334
|
+
}
|
|
4335
|
+
var UnsupportedAgentError2 = class extends Error {
|
|
4336
|
+
constructor(agentId) {
|
|
4337
|
+
super(`Unsupported agent: ${agentId}`);
|
|
4338
|
+
this.name = "UnsupportedAgentError";
|
|
4339
|
+
}
|
|
4340
|
+
};
|
|
4341
|
+
function isConfigObject6(value) {
|
|
4342
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4343
|
+
}
|
|
4344
|
+
function resolveServerMap(document, configKey) {
|
|
4345
|
+
const value = document[configKey];
|
|
4346
|
+
return isConfigObject6(value) ? value : {};
|
|
4347
|
+
}
|
|
4348
|
+
function mergeServerMap(document, configKey, servers) {
|
|
4349
|
+
return { ...document, [configKey]: servers };
|
|
4350
|
+
}
|
|
4351
|
+
function expandHomePath(configPath, homeDir) {
|
|
4352
|
+
if (!configPath.startsWith("~")) {
|
|
4353
|
+
return configPath;
|
|
4354
|
+
}
|
|
4355
|
+
if (configPath === "~") {
|
|
4356
|
+
return homeDir;
|
|
4357
|
+
}
|
|
4358
|
+
if (configPath.startsWith("~/")) {
|
|
4359
|
+
return path23.join(homeDir, configPath.slice(2));
|
|
4360
|
+
}
|
|
4361
|
+
return path23.join(homeDir, configPath.slice(1));
|
|
4362
|
+
}
|
|
4363
|
+
function parseYamlDocument(content) {
|
|
4364
|
+
if (content.trim() === "") {
|
|
4365
|
+
return {};
|
|
4366
|
+
}
|
|
4367
|
+
const parsed = parseYaml3(content);
|
|
4368
|
+
if (parsed === null || parsed === void 0) {
|
|
4369
|
+
return {};
|
|
4370
|
+
}
|
|
4371
|
+
if (!isConfigObject6(parsed)) {
|
|
4372
|
+
throw new Error("Expected YAML document to be an object.");
|
|
4373
|
+
}
|
|
4374
|
+
return parsed;
|
|
4375
|
+
}
|
|
4376
|
+
function serializeYamlDocument(document) {
|
|
4377
|
+
const serialized = stringifyYaml2(document);
|
|
4378
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}
|
|
4379
|
+
`;
|
|
4380
|
+
}
|
|
4381
|
+
async function readYamlConfig(configPath, options) {
|
|
4382
|
+
const absolutePath = expandHomePath(configPath, options.homeDir);
|
|
4383
|
+
const existingContent = await readFileIfExists(options.fs, absolutePath);
|
|
4384
|
+
if (existingContent === null) {
|
|
4385
|
+
return {};
|
|
4386
|
+
}
|
|
4387
|
+
return parseYamlDocument(existingContent);
|
|
4388
|
+
}
|
|
4389
|
+
async function writeYamlConfig(configPath, document, options) {
|
|
4390
|
+
if (options.dryRun) {
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
const absolutePath = expandHomePath(configPath, options.homeDir);
|
|
4394
|
+
const configDir = path23.dirname(absolutePath);
|
|
4395
|
+
await options.fs.mkdir(configDir, { recursive: true });
|
|
4396
|
+
await options.fs.writeFile(absolutePath, serializeYamlDocument(document), {
|
|
4397
|
+
encoding: "utf8"
|
|
4398
|
+
});
|
|
4399
|
+
}
|
|
4400
|
+
function removeServer(document, configKey, serverName) {
|
|
4401
|
+
const servers = resolveServerMap(document, configKey);
|
|
4402
|
+
if (!(serverName in servers)) {
|
|
4403
|
+
return { changed: false, content: document };
|
|
4404
|
+
}
|
|
4405
|
+
const nextServers = { ...servers };
|
|
4406
|
+
delete nextServers[serverName];
|
|
4407
|
+
if (Object.keys(nextServers).length === 0) {
|
|
4408
|
+
const nextDocument = { ...document };
|
|
4409
|
+
delete nextDocument[configKey];
|
|
4410
|
+
return { changed: true, content: nextDocument };
|
|
4411
|
+
}
|
|
4412
|
+
return {
|
|
4413
|
+
changed: true,
|
|
4414
|
+
content: mergeServerMap(document, configKey, nextServers)
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
async function configure2(agentId, server, options) {
|
|
4418
|
+
if (!isSupported(agentId)) {
|
|
4419
|
+
throw new UnsupportedAgentError2(agentId);
|
|
4420
|
+
}
|
|
4421
|
+
const config = getAgentConfig2(agentId);
|
|
4422
|
+
const configPath = resolveConfigPath2(config, options.platform);
|
|
4423
|
+
const shapeTransformer = getShapeTransformer(config.shape);
|
|
4424
|
+
const shaped = shapeTransformer(server);
|
|
4425
|
+
if (shaped === void 0) {
|
|
4426
|
+
await unconfigure2(agentId, server.name, options);
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
if (config.format === "yaml") {
|
|
4430
|
+
const document = await readYamlConfig(configPath, options);
|
|
4431
|
+
const servers = resolveServerMap(document, config.configKey);
|
|
4432
|
+
const nextDocument = mergeServerMap(document, config.configKey, {
|
|
4433
|
+
...servers,
|
|
4434
|
+
[server.name]: shaped
|
|
4435
|
+
});
|
|
4436
|
+
await writeYamlConfig(configPath, nextDocument, options);
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
const configDir = getConfigDirectory(configPath);
|
|
4440
|
+
await runMutations(
|
|
4441
|
+
[
|
|
4442
|
+
fileMutation.ensureDirectory({
|
|
4443
|
+
path: configDir,
|
|
4444
|
+
label: `Ensure directory ${configDir}`
|
|
4445
|
+
}),
|
|
4446
|
+
// Use transform to replace the server entry entirely (not deep-merge)
|
|
4447
|
+
// This ensures old fields like 'args' are removed when switching to array 'command'
|
|
4448
|
+
configMutation.transform({
|
|
4449
|
+
target: configPath,
|
|
4450
|
+
format: config.format,
|
|
4451
|
+
transform: (document) => {
|
|
4452
|
+
const servers = resolveServerMap(document, config.configKey);
|
|
4453
|
+
const newServers = {
|
|
4454
|
+
...servers,
|
|
4455
|
+
[server.name]: shaped
|
|
4456
|
+
};
|
|
4457
|
+
return {
|
|
4458
|
+
changed: true,
|
|
4459
|
+
content: mergeServerMap(document, config.configKey, newServers)
|
|
4460
|
+
};
|
|
4461
|
+
},
|
|
4462
|
+
label: `Add ${server.name} to ${configPath}`
|
|
4463
|
+
})
|
|
4464
|
+
],
|
|
4465
|
+
{
|
|
4466
|
+
fs: options.fs,
|
|
4467
|
+
homeDir: options.homeDir,
|
|
4468
|
+
dryRun: options.dryRun,
|
|
4469
|
+
observers: options.observers
|
|
4470
|
+
}
|
|
4471
|
+
);
|
|
4472
|
+
}
|
|
4473
|
+
async function unconfigure2(agentId, serverName, options) {
|
|
4474
|
+
if (!isSupported(agentId)) {
|
|
4475
|
+
throw new UnsupportedAgentError2(agentId);
|
|
4476
|
+
}
|
|
4477
|
+
const config = getAgentConfig2(agentId);
|
|
4478
|
+
const configPath = resolveConfigPath2(config, options.platform);
|
|
4479
|
+
if (config.format === "yaml") {
|
|
4480
|
+
const document = await readYamlConfig(configPath, options);
|
|
4481
|
+
const { changed, content } = removeServer(
|
|
4482
|
+
document,
|
|
4483
|
+
config.configKey,
|
|
4484
|
+
serverName
|
|
4485
|
+
);
|
|
4486
|
+
if (!changed) {
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
4489
|
+
await writeYamlConfig(configPath, content, options);
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
await runMutations(
|
|
4493
|
+
[
|
|
4494
|
+
configMutation.prune({
|
|
4495
|
+
target: configPath,
|
|
4496
|
+
format: config.format,
|
|
4497
|
+
shape: {
|
|
4498
|
+
[config.configKey]: {
|
|
4499
|
+
[serverName]: {}
|
|
4500
|
+
}
|
|
4501
|
+
},
|
|
4502
|
+
label: `Remove ${serverName} from ${configPath}`
|
|
4503
|
+
})
|
|
4504
|
+
],
|
|
4505
|
+
{
|
|
4506
|
+
fs: options.fs,
|
|
4507
|
+
homeDir: options.homeDir,
|
|
4508
|
+
dryRun: options.dryRun,
|
|
4509
|
+
observers: options.observers
|
|
4510
|
+
}
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
// packages/memory/src/install.ts
|
|
4515
|
+
var SKILL_NAME = "poe-code-memory";
|
|
4516
|
+
async function installMemory(options) {
|
|
4517
|
+
if (options.skillOnly && options.mcpOnly) {
|
|
4518
|
+
throw new Error("--skill-only and --mcp-only cannot be combined.");
|
|
4519
|
+
}
|
|
4520
|
+
const scope = options.scope ?? "local";
|
|
4521
|
+
let skillPath;
|
|
4522
|
+
if (!options.mcpOnly) {
|
|
4523
|
+
const installed = await installSkill(
|
|
4524
|
+
options.agent,
|
|
4525
|
+
{
|
|
4526
|
+
name: SKILL_NAME,
|
|
4527
|
+
content: options.skillContent
|
|
4528
|
+
},
|
|
4529
|
+
{
|
|
4530
|
+
fs: options.fs,
|
|
4531
|
+
cwd: options.cwd,
|
|
4532
|
+
homeDir: options.homeDir,
|
|
4533
|
+
scope,
|
|
4534
|
+
dryRun: options.dryRun,
|
|
4535
|
+
observers: options.observers
|
|
4536
|
+
}
|
|
4537
|
+
);
|
|
4538
|
+
skillPath = installed.displayPath;
|
|
4539
|
+
}
|
|
4540
|
+
let mcpConfigPath;
|
|
4541
|
+
if (!options.skillOnly) {
|
|
4542
|
+
await configure2(
|
|
4543
|
+
options.agent,
|
|
4544
|
+
{
|
|
4545
|
+
name: SKILL_NAME,
|
|
4546
|
+
config: {
|
|
4547
|
+
transport: "stdio",
|
|
4548
|
+
command: "poe-code",
|
|
4549
|
+
args: options.allowWrites ? ["memory-mcp", "--allow-writes"] : ["memory-mcp"]
|
|
4550
|
+
}
|
|
4551
|
+
},
|
|
4552
|
+
{
|
|
4553
|
+
fs: options.fs,
|
|
4554
|
+
homeDir: options.homeDir,
|
|
4555
|
+
platform: options.platform,
|
|
4556
|
+
dryRun: options.dryRun,
|
|
4557
|
+
observers: options.observers
|
|
4558
|
+
}
|
|
4559
|
+
);
|
|
4560
|
+
mcpConfigPath = options.agent === "codex" ? `${options.homeDir}/.config/codex/mcp-config.json` : `${options.homeDir}/.mcp.json`;
|
|
4561
|
+
}
|
|
4562
|
+
return {
|
|
4563
|
+
skillInstalled: !options.mcpOnly,
|
|
4564
|
+
mcpConfigured: !options.skillOnly,
|
|
4565
|
+
skillPath,
|
|
4566
|
+
mcpConfigPath
|
|
4567
|
+
};
|
|
4568
|
+
}
|
|
4569
|
+
|
|
4570
|
+
// packages/memory/src/query.ts
|
|
4571
|
+
import * as fs12 from "node:fs/promises";
|
|
4572
|
+
import path24 from "node:path";
|
|
4573
|
+
async function queryMemory(root, options) {
|
|
4574
|
+
const pages = await listPages(root);
|
|
4575
|
+
if (pages.length === 0) {
|
|
4576
|
+
return {
|
|
4577
|
+
answer: "",
|
|
4578
|
+
citations: [],
|
|
4579
|
+
tokensUsed: 0,
|
|
4580
|
+
budget: options.budget,
|
|
4581
|
+
exitCode: 0
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
const configOptions = {
|
|
4585
|
+
fs: fs12,
|
|
4586
|
+
filePath: path24.join(inferRepoRoot2(root), "poe-code.json")
|
|
4587
|
+
};
|
|
4588
|
+
const agentId = await resolveAgent(configOptions, options.agent ?? null) ?? options.agent ?? "claude-code";
|
|
4589
|
+
const context = await selectQueryContext(root, options.question, options.budget);
|
|
4590
|
+
const spawnFn = options.spawnFn;
|
|
4591
|
+
const result = await spawnFn?.(agentId, context.prompt) ?? {
|
|
4592
|
+
answer: "",
|
|
4593
|
+
citations: [],
|
|
4594
|
+
tokensUsed: context.tokensUsed,
|
|
4595
|
+
budget: options.budget,
|
|
4596
|
+
exitCode: 0
|
|
4597
|
+
};
|
|
4598
|
+
return {
|
|
4599
|
+
answer: result.answer,
|
|
4600
|
+
citations: result.citations,
|
|
4601
|
+
tokensUsed: result.tokensUsed,
|
|
4602
|
+
budget: options.budget,
|
|
4603
|
+
exitCode: result.exitCode
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
async function selectQueryContext(root, question, budget) {
|
|
4607
|
+
const [indexText, pages] = await Promise.all([
|
|
4608
|
+
fs12.readFile(path24.join(root, MEMORY_INDEX_RELPATH), "utf8"),
|
|
4609
|
+
listPages(root)
|
|
4610
|
+
]);
|
|
4611
|
+
const indexTokens = countTokens(indexText);
|
|
4612
|
+
if (indexTokens > budget) {
|
|
4613
|
+
throw new Error(`budget too small; needs at least ${indexTokens} tokens`);
|
|
4614
|
+
}
|
|
4615
|
+
const rankedPages = rankPagesForQuery(pages, question);
|
|
4616
|
+
const selectedPages = [];
|
|
4617
|
+
let tokensUsed = indexTokens;
|
|
4618
|
+
let truncated = false;
|
|
4619
|
+
for (const page of rankedPages) {
|
|
4620
|
+
const pageTokens = countTokens(renderPageContext(page));
|
|
4621
|
+
if (tokensUsed + pageTokens > budget) {
|
|
4622
|
+
truncated = true;
|
|
4623
|
+
continue;
|
|
4624
|
+
}
|
|
4625
|
+
selectedPages.push(page);
|
|
4626
|
+
tokensUsed += pageTokens;
|
|
4627
|
+
}
|
|
4628
|
+
return {
|
|
4629
|
+
prompt: buildQueryPrompt(question, indexText, selectedPages),
|
|
4630
|
+
selectedPages,
|
|
4631
|
+
tokensUsed,
|
|
4632
|
+
truncated
|
|
4633
|
+
};
|
|
4634
|
+
}
|
|
4635
|
+
function rankPagesForQuery(pages, question) {
|
|
4636
|
+
const terms = tokenize(question);
|
|
4637
|
+
const documents = pages.map((page) => {
|
|
4638
|
+
const text = [page.relPath, page.frontmatter.name ?? "", page.frontmatter.description ?? "", page.body].join("\n").toLowerCase();
|
|
4639
|
+
const tokens = tokenize(text);
|
|
4640
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4641
|
+
for (const token of tokens) {
|
|
4642
|
+
counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
4643
|
+
}
|
|
4644
|
+
return { page, counts, size: tokens.length };
|
|
4645
|
+
});
|
|
4646
|
+
const docFrequency = /* @__PURE__ */ new Map();
|
|
4647
|
+
for (const term of terms) {
|
|
4648
|
+
let count = 0;
|
|
4649
|
+
for (const document of documents) {
|
|
4650
|
+
if ((document.counts.get(term) ?? 0) > 0) {
|
|
4651
|
+
count += 1;
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
docFrequency.set(term, count);
|
|
4655
|
+
}
|
|
4656
|
+
return documents.map((document) => ({
|
|
4657
|
+
page: document.page,
|
|
4658
|
+
score: terms.reduce((total, term) => {
|
|
4659
|
+
const tf = (document.counts.get(term) ?? 0) / Math.max(document.size, 1);
|
|
4660
|
+
const idf = Math.log((documents.length + 1) / ((docFrequency.get(term) ?? 0) + 1)) + 1;
|
|
4661
|
+
return total + tf * idf;
|
|
4662
|
+
}, 0)
|
|
4663
|
+
})).sort((left, right) => right.score - left.score || left.page.relPath.localeCompare(right.page.relPath)).map((entry) => entry.page);
|
|
4664
|
+
}
|
|
4665
|
+
function buildQueryPrompt(question, indexText, pages) {
|
|
4666
|
+
const renderedPages = pages.map((page) => renderPageContext(page)).join("\n\n");
|
|
4667
|
+
return [
|
|
4668
|
+
"Answer using only the provided memory pages.",
|
|
4669
|
+
"Cite pages and sections with [rel_path \xA7section]. If memory does not answer the question, say so.",
|
|
4670
|
+
"No tools are available.",
|
|
4671
|
+
"",
|
|
4672
|
+
`Question: ${question}`,
|
|
4673
|
+
"",
|
|
4674
|
+
"INDEX.md",
|
|
4675
|
+
indexText,
|
|
4676
|
+
"",
|
|
4677
|
+
renderedPages
|
|
4678
|
+
].join("\n");
|
|
4679
|
+
}
|
|
4680
|
+
function renderPageContext(page) {
|
|
4681
|
+
return [`FILE: ${page.relPath}`, page.body].join("\n");
|
|
4682
|
+
}
|
|
4683
|
+
function tokenize(text) {
|
|
4684
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length > 0);
|
|
4685
|
+
}
|
|
4686
|
+
function inferRepoRoot2(root) {
|
|
4687
|
+
return path24.resolve(root, "..", "..");
|
|
4688
|
+
}
|
|
4689
|
+
|
|
4690
|
+
// packages/memory/src/explain.ts
|
|
4691
|
+
async function explainPage(root, options) {
|
|
4692
|
+
const targetPage = await readPageIfPresent(root, options.relPath);
|
|
4693
|
+
if (targetPage === void 0) {
|
|
4694
|
+
return {
|
|
4695
|
+
answer: "",
|
|
4696
|
+
citations: [],
|
|
4697
|
+
tokensUsed: 0,
|
|
4698
|
+
budget: options.budget,
|
|
4699
|
+
exitCode: 0,
|
|
4700
|
+
inboundPages: [],
|
|
4701
|
+
outboundSources: []
|
|
4702
|
+
};
|
|
4703
|
+
}
|
|
4704
|
+
const allContext = await selectQueryContext(root, options.relPath, Number.MAX_SAFE_INTEGER);
|
|
4705
|
+
const relatedPages = collectRelatedPages(allContext.selectedPages, targetPage.relPath, targetPage.frontmatter.sources ?? []);
|
|
4706
|
+
const prompt = buildExplainPrompt(targetPage.relPath, relatedPages);
|
|
4707
|
+
const tokensUsed = countTokens(prompt);
|
|
4708
|
+
if (tokensUsed > options.budget) {
|
|
4709
|
+
throw new Error(`budget too small; needs at least ${tokensUsed} tokens`);
|
|
4710
|
+
}
|
|
4711
|
+
const spawnFn = options.spawnFn;
|
|
4712
|
+
const response = await queryMemory(root, {
|
|
4713
|
+
question: `explain ${options.relPath}`,
|
|
4714
|
+
budget: options.budget,
|
|
4715
|
+
agent: options.agent,
|
|
4716
|
+
spawnFn: spawnFn === void 0 ? void 0 : async (...args) => {
|
|
4717
|
+
const [agent] = args;
|
|
4718
|
+
return spawnFn(agent, prompt);
|
|
4719
|
+
}
|
|
4720
|
+
});
|
|
4721
|
+
return {
|
|
4722
|
+
...response,
|
|
4723
|
+
inboundPages: relatedPages.filter((page) => page.relPath !== targetPage.relPath).filter((page) => (page.frontmatter.sources ?? []).some((source) => source.path === targetPage.relPath)).map((page) => page.relPath),
|
|
4724
|
+
outboundSources: targetPage.frontmatter.sources ?? []
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4727
|
+
function collectRelatedPages(pages, targetRelPath, outboundSources) {
|
|
4728
|
+
const memorySourcePaths = new Set(outboundSources.map((source) => source.path));
|
|
4729
|
+
return pages.filter((page) => {
|
|
4730
|
+
if (page.relPath === targetRelPath) {
|
|
4731
|
+
return true;
|
|
4732
|
+
}
|
|
4733
|
+
if (memorySourcePaths.has(page.relPath)) {
|
|
4734
|
+
return true;
|
|
4735
|
+
}
|
|
4736
|
+
return (page.frontmatter.sources ?? []).some((source) => source.path === targetRelPath);
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4739
|
+
function buildExplainPrompt(targetRelPath, pages) {
|
|
4740
|
+
return [
|
|
4741
|
+
"Summarize the target page using only the provided memory pages.",
|
|
4742
|
+
"Return a 1-2 paragraph explanation plus the important inbound/outbound links.",
|
|
4743
|
+
"Cite pages and sections with [rel_path \xA7section]. If memory is insufficient, say so.",
|
|
4744
|
+
"No tools are available.",
|
|
4745
|
+
"",
|
|
4746
|
+
`Target page: ${targetRelPath}`,
|
|
4747
|
+
"",
|
|
4748
|
+
...pages.map((page) => [`FILE: ${page.relPath}`, page.body].join("\n"))
|
|
4749
|
+
].join("\n\n");
|
|
4750
|
+
}
|
|
4751
|
+
async function readPageIfPresent(root, relPath) {
|
|
4752
|
+
try {
|
|
4753
|
+
return await readPage(root, relPath);
|
|
4754
|
+
} catch (error) {
|
|
4755
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
4756
|
+
return void 0;
|
|
4757
|
+
}
|
|
4758
|
+
throw error;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
// packages/memory/src/explain.cli.ts
|
|
4763
|
+
async function runMemoryExplain(input) {
|
|
4764
|
+
return explainPage(input.root, {
|
|
4765
|
+
relPath: input.relPath,
|
|
4766
|
+
budget: input.budget,
|
|
4767
|
+
agent: input.agent,
|
|
4768
|
+
spawnFn: input.spawnFn
|
|
4769
|
+
});
|
|
4770
|
+
}
|
|
4771
|
+
export {
|
|
4772
|
+
INGEST_PROMPT_VERSION,
|
|
4773
|
+
MEMORY_ROOT_ENV_VAR,
|
|
4774
|
+
appendToPage,
|
|
4775
|
+
auditClaims,
|
|
4776
|
+
clearCache,
|
|
4777
|
+
clearMemory,
|
|
4778
|
+
computeIngestKey,
|
|
4779
|
+
computeTokenStats,
|
|
4780
|
+
editPage,
|
|
4781
|
+
explainPage,
|
|
4782
|
+
ingest,
|
|
4783
|
+
initMemory,
|
|
4784
|
+
installMemory,
|
|
4785
|
+
listPages,
|
|
4786
|
+
parseClaims,
|
|
4787
|
+
printMcpConfig,
|
|
4788
|
+
queryMemory,
|
|
4789
|
+
rankPagesForQuery,
|
|
4790
|
+
readCacheEntry,
|
|
4791
|
+
readPage,
|
|
4792
|
+
reconcile,
|
|
4793
|
+
resolveConfiguredMemoryRoot,
|
|
4794
|
+
resolveMemoryRoot,
|
|
4795
|
+
runMemoryCacheClear,
|
|
4796
|
+
runMemoryCacheStatus,
|
|
4797
|
+
runMemoryExplain,
|
|
4798
|
+
searchMemory,
|
|
4799
|
+
selectQueryContext,
|
|
4800
|
+
serializeTag,
|
|
4801
|
+
snapshot,
|
|
4802
|
+
startMemoryMcpServer,
|
|
4803
|
+
statusOf,
|
|
4804
|
+
writeCacheEntry,
|
|
4805
|
+
writePage
|
|
4806
|
+
};
|
|
4807
|
+
//# sourceMappingURL=index.js.map
|