@rayul/kordis 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +1 -1
- package/assets/AGENTS.md +108 -0
- package/assets/MEMGINEERING.md +3 -1
- package/assets/kordis-publishing/SKILL.md +115 -45
- package/assets/kordis-recommend/SKILL.md +2 -1
- package/assets/kordis-social/SKILL.md +19 -0
- package/assets/kordis-using/SKILL.md +7 -1
- package/assets/memgineering-recall/SKILL.md +85 -14
- package/assets/memgineering-rules/SKILL.md +59 -15
- package/assets/memgineering-setup/SKILL.md +76 -22
- package/assets/memgineering-writing/SKILL.md +68 -17
- package/dist/index.js +1427 -209
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -134,12 +134,32 @@ var init_enums = __esm({
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
// ../../packages/contracts/src/refresh-race.ts
|
|
138
|
+
function isRefreshRace(body) {
|
|
139
|
+
if (typeof body !== "object" || body === null) return false;
|
|
140
|
+
const error = body.error;
|
|
141
|
+
if (typeof error !== "object" || error === null) return false;
|
|
142
|
+
const { code, flags } = error;
|
|
143
|
+
if (code !== "not_authenticated" || !Array.isArray(flags)) return false;
|
|
144
|
+
return flags.some(
|
|
145
|
+
(flag2) => typeof flag2 === "object" && flag2 !== null && flag2.code === REFRESH_RACE_FLAG
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
var REFRESH_RACE_FLAG;
|
|
149
|
+
var init_refresh_race = __esm({
|
|
150
|
+
"../../packages/contracts/src/refresh-race.ts"() {
|
|
151
|
+
"use strict";
|
|
152
|
+
REFRESH_RACE_FLAG = "refresh_rotated_by_another_request";
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
137
156
|
// ../../packages/contracts/src/errors.ts
|
|
138
157
|
import { z as z3 } from "zod";
|
|
139
158
|
var ErrorCodeSchema, ERROR_EXIT_CODES, ReviewFlagSchema, ApiErrorSchema;
|
|
140
159
|
var init_errors = __esm({
|
|
141
160
|
"../../packages/contracts/src/errors.ts"() {
|
|
142
161
|
"use strict";
|
|
162
|
+
init_refresh_race();
|
|
143
163
|
ErrorCodeSchema = z3.enum([
|
|
144
164
|
"generic_failure",
|
|
145
165
|
// 1
|
|
@@ -1159,16 +1179,33 @@ var init_api = __esm({
|
|
|
1159
1179
|
*/
|
|
1160
1180
|
content: z11.string().min(50).max(1e5),
|
|
1161
1181
|
/**
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1182
|
+
* Optional, and refused when it disagrees. core-api stores the title,
|
|
1183
|
+
* description and type it reads from the frontmatter of `content`, which must
|
|
1184
|
+
* carry all three (the title may come from `name:`). A value here that
|
|
1185
|
+
* differs from the frontmatter's, compared trimmed, is refused as
|
|
1186
|
+
* `usage_error` (HTTP 400) with a hint naming the frontmatter key; absent
|
|
1187
|
+
* means no claim. A `type` the registry cannot publish at all (`scoped-rule`,
|
|
1188
|
+
* `mdc-rule`, `plugin`) names no key to write: its hint says the type cannot
|
|
1189
|
+
* be published yet and to drop `--type`. `kordis push` ≤0.15.2 sends
|
|
1190
|
+
* `--type` here, and writes `type: skill` into the frontmatter of a SKILL.md
|
|
1191
|
+
* or *.skill.md file, a folder or a .skill bundle that names no type — so
|
|
1192
|
+
* `--type hook` on one is refused, where until 2026-10 it published a skill.
|
|
1193
|
+
* Later versions send no `type`: they write `--type` into the frontmatter.
|
|
1164
1194
|
*/
|
|
1165
1195
|
title: z11.string().min(1).max(200).optional(),
|
|
1166
1196
|
description: z11.string().min(1).max(2048).optional(),
|
|
1167
1197
|
type: HarnessTypeSchema.optional(),
|
|
1198
|
+
/**
|
|
1199
|
+
* A new package is published under this slug instead of the one derived from
|
|
1200
|
+
* the frontmatter title. An update keeps the slug of the package it updates:
|
|
1201
|
+
* a slug here that differs from the target's is refused (`usage_error`).
|
|
1202
|
+
*/
|
|
1168
1203
|
slug: SlugSchema.optional(),
|
|
1169
1204
|
/**
|
|
1170
|
-
* When updating, the target full_slug
|
|
1171
|
-
*
|
|
1205
|
+
* When updating, the target full_slug; its presence is what makes a publish
|
|
1206
|
+
* an update (`PUT /api/packages/:u/:s` sets it from the path). Omitted for a
|
|
1207
|
+
* new package, whose slug is `slug` or else derived from the frontmatter
|
|
1208
|
+
* title — never derived on an update.
|
|
1172
1209
|
*/
|
|
1173
1210
|
target_full_slug: FullSlugSchema.optional(),
|
|
1174
1211
|
/**
|
|
@@ -1176,6 +1213,12 @@ var init_api = __esm({
|
|
|
1176
1213
|
*/
|
|
1177
1214
|
bump: z11.enum(["patch", "minor", "major"]).optional(),
|
|
1178
1215
|
changelog: z11.string().max(2e3).optional(),
|
|
1216
|
+
/**
|
|
1217
|
+
* Refused when non-empty (`usage_error`): a package's dependencies are read
|
|
1218
|
+
* from its frontmatter's `depends:` and `recommends:` lists only. Kept in the
|
|
1219
|
+
* schema so the server sees them to refuse them — this object is non-strict,
|
|
1220
|
+
* and an unlisted key would be stripped without a word.
|
|
1221
|
+
*/
|
|
1179
1222
|
depends: z11.array(z11.string()).optional(),
|
|
1180
1223
|
recommends: z11.array(z11.string()).optional(),
|
|
1181
1224
|
/**
|
|
@@ -1675,9 +1718,122 @@ var init_telemetry = __esm({
|
|
|
1675
1718
|
});
|
|
1676
1719
|
|
|
1677
1720
|
// ../../packages/contracts/src/review-patterns.ts
|
|
1721
|
+
var STAGE_1_REJECT_PATTERNS, STAGE_1_5_AUP_PATTERNS, DETERMINISTIC_REJECT_PATTERNS;
|
|
1678
1722
|
var init_review_patterns = __esm({
|
|
1679
1723
|
"../../packages/contracts/src/review-patterns.ts"() {
|
|
1680
1724
|
"use strict";
|
|
1725
|
+
STAGE_1_REJECT_PATTERNS = [
|
|
1726
|
+
{
|
|
1727
|
+
code: "curl_pipe_shell",
|
|
1728
|
+
pattern: /\bcurl\s+[^|;]+\s*\|\s*(?:(?:sudo|doas)\s+)?(?:-E\s+|[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:sh|bash|zsh|ksh|dash|csh|tcsh|fish)\b/i,
|
|
1729
|
+
hint: "Replace `curl \u2026 | bash` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
code: "wget_pipe_shell",
|
|
1733
|
+
pattern: /\bwget\s+[^|;]+\s*\|\s*(?:(?:sudo|doas)\s+)?(?:-E\s+|[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:sh|bash|zsh|ksh|dash|csh|tcsh|fish)\b/i,
|
|
1734
|
+
hint: "Replace `wget \u2026 | bash` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
code: "shell_process_substitution",
|
|
1738
|
+
pattern: /(?:bash|sh|zsh)\s*<\(\s*(?:curl|wget)\b/i,
|
|
1739
|
+
hint: "Replace `bash <(curl \u2026)` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
code: "eval_atob",
|
|
1743
|
+
pattern: /\beval\s*\(\s*atob\b/i,
|
|
1744
|
+
hint: "Encoded eval (`eval(atob(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
code: "eval_buffer_from",
|
|
1748
|
+
pattern: /\beval\s*\(\s*Buffer\.from\b/i,
|
|
1749
|
+
hint: "Encoded eval (`eval(Buffer.from(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
code: "aws_access_key",
|
|
1753
|
+
pattern: /\b(?:AKIA|ASIA|AGPA)[0-9A-Z]{16}\b/,
|
|
1754
|
+
hint: "Remove the AWS access key from the body. Reference credentials via env var (`AWS_ACCESS_KEY_ID`); rotate the leaked key immediately if it was real."
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
code: "private_key_block",
|
|
1758
|
+
pattern: /-----BEGIN\s+(?:[A-Z0-9]+\s+)?PRIVATE\s+KEY(?:\s+BLOCK)?-----/,
|
|
1759
|
+
hint: "Remove the private key block from the body. Reference keys via env var or a secret manager; rotate the leaked key immediately if it was real."
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
code: "google_api_key",
|
|
1763
|
+
pattern: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
1764
|
+
hint: "Remove the Google API key from the body. Reference credentials via env var; rotate the leaked key immediately if it was real."
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
code: "slack_token",
|
|
1768
|
+
pattern: /\b(?:xox[abpors]|xapp|xoxe)-[0-9a-zA-Z-]{10,}\b/,
|
|
1769
|
+
hint: "Remove the Slack token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
code: "github_token",
|
|
1773
|
+
pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{36,}|github_pat_[A-Za-z0-9_]{60,})\b/,
|
|
1774
|
+
hint: "Remove the GitHub token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
1775
|
+
}
|
|
1776
|
+
];
|
|
1777
|
+
STAGE_1_5_AUP_PATTERNS = [
|
|
1778
|
+
{
|
|
1779
|
+
// Stolen-card / bank-credential trafficking storefronts. Anchored to the
|
|
1780
|
+
// trafficking jargon that only appears in carding markets, not in the
|
|
1781
|
+
// defensive "how to detect carding" literature. `dumps` is also an
|
|
1782
|
+
// ordinary developer verb — "this hook dumps vendor files", "dumps store
|
|
1783
|
+
// state", "dumps market data to CSV" all matched the shared noun list —
|
|
1784
|
+
// so after `dumps` only sale framing (`shop`, `for sale`) counts, while
|
|
1785
|
+
// `cvv` and `fullz` keep the full list. Narrowed when comments started
|
|
1786
|
+
// running this table (2026-10-14).
|
|
1787
|
+
code: "aup_stolen_financial_data_market",
|
|
1788
|
+
pattern: /\b(?:cvv|fullz)\s+(?:shop|store|for\s+sale|market|vendor)\b|\bdumps\s+(?:shop|for\s+sale)\b|\bdumps?\s+with\s+pin\b|\bsell(?:ing)?\s+(?:cvv|fullz|dumps)\b/i,
|
|
1789
|
+
hint: "This reads as a storefront for trafficking stolen financial data, which violates the Kordis AUP. If the package is about DETECTING or DEFENDING against card fraud, reword it so it does not read as an offer to buy/sell stolen data."
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
// DDoS-for-hire (booter/stresser) services sold on a subscription.
|
|
1793
|
+
code: "aup_ddos_for_hire",
|
|
1794
|
+
pattern: /\b(?:booter|stresser)\s+(?:service|panel|subscription)\b|\b(?:ddos|dstat)\s+for\s+hire\b|\bhire\s+(?:a\s+)?(?:booter|stresser|ddos)\b/i,
|
|
1795
|
+
hint: "This reads as a DDoS-for-hire (booter/stresser) service, which violates the Kordis AUP. Load-testing tooling is fine \u2014 describe it as testing infrastructure you own/are authorized to test, not a for-hire attack service."
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
// Malware/stealer/ransomware sold or distributed as a product ("builder
|
|
1799
|
+
// for sale", "buy our stealer"). A skill that DEFENDS against these, or a
|
|
1800
|
+
// malware-analysis skill, does not use buy/sell framing.
|
|
1801
|
+
code: "aup_malware_sale",
|
|
1802
|
+
pattern: /\b(?:ransomware|stealer|infostealer|rat|keylogger|botnet)\s+(?:builder|kit|panel|source)\s+(?:for\s+sale|for\s+rent)\b|\b(?:buy|sell(?:ing)?|rent)\s+(?:our\s+)?(?:ransomware|stealer|infostealer|botnet)\b/i,
|
|
1803
|
+
hint: "This reads as an offer to sell/rent malware, which violates the Kordis AUP. Malware ANALYSIS and DEFENSE content is welcome \u2014 reword so it does not read as distributing a weaponized build."
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
// Child sexual abuse material. Unlike the rest of this table this entry is
|
|
1807
|
+
// NOT anchored to solicitation: it matches the spelled-out phrases ("child
|
|
1808
|
+
// porn", "child pornography", "child sexual abuse material") wherever they
|
|
1809
|
+
// appear, child-safety and moderation education included. Only the
|
|
1810
|
+
// acronym "CSAM" on its own does not match ("how platforms detect CSAM
|
|
1811
|
+
// for NCMEC reporting" passes). Kept this broad on purpose (2026-10-14):
|
|
1812
|
+
// comments have no Stage-2 review after this table, so a miss costs more
|
|
1813
|
+
// than a false positive.
|
|
1814
|
+
code: "aup_csam",
|
|
1815
|
+
pattern: /\bchild\s+(?:porn(?:ography)?|sexual\s+abuse\s+material)\b/i,
|
|
1816
|
+
hint: "This reads as solicitation or distribution of child sexual abuse material, which is illegal and violates the Kordis AUP. This is a terminal, non-appealable rejection."
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
// Drug / untraceable-weapon brokering with explicit no-id / untraceable /
|
|
1820
|
+
// dark-web fulfillment language — not a bare mention of a substance or
|
|
1821
|
+
// firearm topic (e.g. harm-reduction or policy content does not use this
|
|
1822
|
+
// buy/sell + untraceable-fulfillment framing together).
|
|
1823
|
+
code: "aup_drug_weapon_brokering",
|
|
1824
|
+
pattern: /\b(?:buy|sell(?:ing)?)\b[\s\S]{0,40}\b(?:cocaine|heroin|fentanyl|methamphetamine|untraceable firearms?|untraceable guns?)\b[\s\S]{0,40}\b(?:no id|untraceable|worldwide shipping|dark ?web)\b/i,
|
|
1825
|
+
hint: "This reads as brokering illegal drugs or untraceable weapons for sale, which violates the Kordis AUP. If the package is about DETECTING or REPORTING such activity, reword it so it does not read as an offer to buy/sell."
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
// A directive to dox or swat a specific, named/tagged target — not a
|
|
1829
|
+
// bare mention of doxxing/swatting as a topic (e.g. "how to protect
|
|
1830
|
+
// yourself from doxxing" does not match).
|
|
1831
|
+
code: "aup_targeted_harassment_directive",
|
|
1832
|
+
pattern: /\b(?:dox|swat)\s+(?:this (?:guy|girl|person|user)|him|her|@[\w.]+)\b/i,
|
|
1833
|
+
hint: "This reads as a directive to dox or swat a specific target, which violates the Kordis AUP. Remove the targeted-harassment instruction."
|
|
1834
|
+
}
|
|
1835
|
+
];
|
|
1836
|
+
DETERMINISTIC_REJECT_PATTERNS = [...STAGE_1_REJECT_PATTERNS, ...STAGE_1_5_AUP_PATTERNS];
|
|
1681
1837
|
}
|
|
1682
1838
|
});
|
|
1683
1839
|
|
|
@@ -2443,13 +2599,17 @@ var init_sections = __esm({
|
|
|
2443
2599
|
|
|
2444
2600
|
// ../../packages/memory-engine/src/schema.ts
|
|
2445
2601
|
import { z as z22 } from "zod";
|
|
2446
|
-
|
|
2602
|
+
function isGlobalScope(scope) {
|
|
2603
|
+
return scope != null && scope.trim().toLowerCase() === GLOBAL_SCOPE;
|
|
2604
|
+
}
|
|
2605
|
+
var MappingModeSchema, ScopeSourceSchema, SCOPE_LIMIT, GLOBAL_SCOPE, TELEMETRY_ERROR_KINDS, TELEMETRY_MESSAGE_LIMIT, TelemetryErrorPayloadSchema2, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
2447
2606
|
var init_schema = __esm({
|
|
2448
2607
|
"../../packages/memory-engine/src/schema.ts"() {
|
|
2449
2608
|
"use strict";
|
|
2450
2609
|
MappingModeSchema = z22.enum(["explicit", "heuristic"]);
|
|
2451
2610
|
ScopeSourceSchema = z22.enum(["explicit", "path", "none"]);
|
|
2452
2611
|
SCOPE_LIMIT = 128;
|
|
2612
|
+
GLOBAL_SCOPE = "global";
|
|
2453
2613
|
TELEMETRY_ERROR_KINDS = [
|
|
2454
2614
|
"network_unreachable",
|
|
2455
2615
|
"server_error",
|
|
@@ -2529,6 +2689,26 @@ var init_schema = __esm({
|
|
|
2529
2689
|
scope: z22.string().min(1).nullable(),
|
|
2530
2690
|
/** Id of the claim that is current for this memory, when one is declared. */
|
|
2531
2691
|
current_claim: z22.string().min(1).nullable(),
|
|
2692
|
+
/**
|
|
2693
|
+
* The end date the NOTE declares, for the notes that state no claim.
|
|
2694
|
+
*
|
|
2695
|
+
* `valid_until` lived only on `Claim`, and the mapper mints a claim only for
|
|
2696
|
+
* a note carrying a `claim:` key — which `remember` has never written. So on
|
|
2697
|
+
* a real brain most notes could not carry an end date that anything read:
|
|
2698
|
+
* sampled six binding rules on a production brain and four had no claim at
|
|
2699
|
+
* all, the two that did having been through `revise`. A date in the
|
|
2700
|
+
* frontmatter of the other four was simply ignored.
|
|
2701
|
+
*
|
|
2702
|
+
* Optional rather than nullable, like `kind` and `binding` above and for the
|
|
2703
|
+
* same reason: a note written before this existed must map to exactly the
|
|
2704
|
+
* `Memory` it always did, or the regeneration test's byte comparison rewrites
|
|
2705
|
+
* every cache in existence on upgrade.
|
|
2706
|
+
*
|
|
2707
|
+
* The claim's copy WINS where both exist — it is the more specific statement,
|
|
2708
|
+
* and a note whose conclusion carries its own dates is saying something about
|
|
2709
|
+
* that conclusion rather than about the file.
|
|
2710
|
+
*/
|
|
2711
|
+
valid_until: z22.string().min(1).optional(),
|
|
2532
2712
|
status: MemoryStatusSchema,
|
|
2533
2713
|
/**
|
|
2534
2714
|
* Whether this memory binds what an agent does, rather than informing it.
|
|
@@ -2610,6 +2790,104 @@ var init_schema = __esm({
|
|
|
2610
2790
|
}
|
|
2611
2791
|
});
|
|
2612
2792
|
|
|
2793
|
+
// ../../packages/memory-engine/src/layout.ts
|
|
2794
|
+
var OBSERVATIONS_DIR, DERIVED_DIR, DERIVED_BLUEPRINT;
|
|
2795
|
+
var init_layout = __esm({
|
|
2796
|
+
"../../packages/memory-engine/src/layout.ts"() {
|
|
2797
|
+
"use strict";
|
|
2798
|
+
OBSERVATIONS_DIR = "_observations";
|
|
2799
|
+
DERIVED_DIR = "_derived";
|
|
2800
|
+
DERIVED_BLUEPRINT = "_blueprint.json";
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
// ../../packages/memory-engine/src/rule-budget.ts
|
|
2805
|
+
function ruleLane(scope, project) {
|
|
2806
|
+
if (scope == null || scope.trim() === "" || isGlobalScope(scope)) return "global";
|
|
2807
|
+
if (project === null) return "elsewhere";
|
|
2808
|
+
return scope.trim().toLowerCase() === project.trim().toLowerCase() ? "project" : "elsewhere";
|
|
2809
|
+
}
|
|
2810
|
+
function declaredScopeOf(memory) {
|
|
2811
|
+
return memory.provenance.scopeSource === "explicit" ? memory.scope : null;
|
|
2812
|
+
}
|
|
2813
|
+
function splitRuleBudget(ranked, scopeOf, project, budget = DEFAULT_RULE_BUDGET) {
|
|
2814
|
+
const globalLane = [];
|
|
2815
|
+
const projectLane = [];
|
|
2816
|
+
const elsewhere = [];
|
|
2817
|
+
for (const item of ranked) {
|
|
2818
|
+
const lane = ruleLane(scopeOf(item), project);
|
|
2819
|
+
if (lane === "global") globalLane.push(item);
|
|
2820
|
+
else if (lane === "project") projectLane.push(item);
|
|
2821
|
+
else elsewhere.push(item);
|
|
2822
|
+
}
|
|
2823
|
+
const total = budget.global + budget.project;
|
|
2824
|
+
const globalRoom = Math.max(budget.global, total - Math.min(projectLane.length, budget.project));
|
|
2825
|
+
const projectRoom = Math.max(budget.project, total - Math.min(globalLane.length, budget.global));
|
|
2826
|
+
return {
|
|
2827
|
+
global: globalLane.slice(0, globalRoom),
|
|
2828
|
+
project: projectLane.slice(0, projectRoom),
|
|
2829
|
+
elsewhere,
|
|
2830
|
+
// Everything either budget COULD have shown, so "5 of 12" is honest about
|
|
2831
|
+
// the cap without counting another project's rules as things withheld.
|
|
2832
|
+
eligible: globalLane.length + projectLane.length
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
var DEFAULT_RULE_BUDGET;
|
|
2836
|
+
var init_rule_budget = __esm({
|
|
2837
|
+
"../../packages/memory-engine/src/rule-budget.ts"() {
|
|
2838
|
+
"use strict";
|
|
2839
|
+
init_schema();
|
|
2840
|
+
DEFAULT_RULE_BUDGET = { global: 5, project: 5 };
|
|
2841
|
+
}
|
|
2842
|
+
});
|
|
2843
|
+
|
|
2844
|
+
// ../../packages/memory-engine/src/rule-text.ts
|
|
2845
|
+
function ruleTextOf(entry) {
|
|
2846
|
+
const intro = (entry.chunks ?? []).find((chunk) => chunk.label === null)?.text.trim();
|
|
2847
|
+
if (intro !== void 0 && intro !== "") return intro;
|
|
2848
|
+
return entry.memory.summary ?? entry.memory.title;
|
|
2849
|
+
}
|
|
2850
|
+
function ruleWidthFor(count) {
|
|
2851
|
+
if (count <= 0) return RULE_WIDTH_MIN;
|
|
2852
|
+
const share = Math.floor(RULE_BLOCK_BUDGET / count);
|
|
2853
|
+
return Math.min(RULE_WIDTH_MAX, Math.max(RULE_WIDTH_MIN, share));
|
|
2854
|
+
}
|
|
2855
|
+
function rulePassage(text, max) {
|
|
2856
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
2857
|
+
const chars = [...flat];
|
|
2858
|
+
if (chars.length <= max) return flat;
|
|
2859
|
+
if (max <= 1) return "\u2026";
|
|
2860
|
+
const budget = max - 1;
|
|
2861
|
+
if (budget < 2) return `${chars.slice(0, budget).join("")}\u2026`;
|
|
2862
|
+
const tailBudget = Math.min(budget - 1, Math.max(1, Math.floor(budget * 0.6)));
|
|
2863
|
+
const tail = [...trailingSentences(flat, tailBudget)];
|
|
2864
|
+
const head = chars.slice(0, budget - tail.length).join("").trimEnd();
|
|
2865
|
+
return `${head}\u2026${tail.join("")}`;
|
|
2866
|
+
}
|
|
2867
|
+
function trailingSentences(flat, budget) {
|
|
2868
|
+
const starts = [...flat.matchAll(/[.!?。!?](?=\s)/g)].map(
|
|
2869
|
+
(match) => match.index + match[0].length
|
|
2870
|
+
);
|
|
2871
|
+
let best = null;
|
|
2872
|
+
for (let i = starts.length - 1; i >= 0; i -= 1) {
|
|
2873
|
+
const candidate = flat.slice(starts[i]).trim();
|
|
2874
|
+
if ([...candidate].length > budget) break;
|
|
2875
|
+
if (candidate !== "") best = candidate;
|
|
2876
|
+
}
|
|
2877
|
+
if (best !== null) return best;
|
|
2878
|
+
return [...flat].slice(-budget).join("");
|
|
2879
|
+
}
|
|
2880
|
+
var RULE_WIRE_WIDTH, RULE_WIDTH_MIN, RULE_WIDTH_MAX, RULE_BLOCK_BUDGET;
|
|
2881
|
+
var init_rule_text = __esm({
|
|
2882
|
+
"../../packages/memory-engine/src/rule-text.ts"() {
|
|
2883
|
+
"use strict";
|
|
2884
|
+
RULE_WIRE_WIDTH = 600;
|
|
2885
|
+
RULE_WIDTH_MIN = 200;
|
|
2886
|
+
RULE_WIDTH_MAX = 600;
|
|
2887
|
+
RULE_BLOCK_BUDGET = RULE_WIDTH_MIN * 10;
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2613
2891
|
// ../../packages/memory-engine/src/boundary.ts
|
|
2614
2892
|
function normalizeForMatch(value) {
|
|
2615
2893
|
return value.normalize("NFC").replace(ZERO_WIDTH, "").replace(WHITESPACE_RUN, " ").trim();
|
|
@@ -2753,8 +3031,8 @@ function scopeFromPath(path2) {
|
|
|
2753
3031
|
return parts.length >= 2 ? parts[parts.length - 2] ?? null : null;
|
|
2754
3032
|
}
|
|
2755
3033
|
function titleFromPath(path2) {
|
|
2756
|
-
const
|
|
2757
|
-
return
|
|
3034
|
+
const base2 = path2.split("/").pop() ?? path2;
|
|
3035
|
+
return base2.replace(/\.[^.]+$/, "") || base2;
|
|
2758
3036
|
}
|
|
2759
3037
|
function h1Of(intro) {
|
|
2760
3038
|
for (const line of intro.split("\n")) {
|
|
@@ -2868,8 +3146,8 @@ function mapNoteToMemory(input) {
|
|
|
2868
3146
|
const ns = explicit ? nsRaw : {};
|
|
2869
3147
|
const id = asString(ns["id"]) ?? path2;
|
|
2870
3148
|
const title = asString(ns["title"]) ?? asString(frontmatter?.["title"]) ?? h1Of(outline.intro) ?? titleFromPath(path2);
|
|
2871
|
-
const
|
|
2872
|
-
const scope =
|
|
3149
|
+
const declaredScope2 = asString(ns["scope"]);
|
|
3150
|
+
const scope = declaredScope2 ?? scopeFromPath(path2);
|
|
2873
3151
|
const summary = asString(ns["summary"]) ?? outline.description ?? firstParagraph(outline.intro) ?? firstSectionParagraph(outline.sections) ?? null;
|
|
2874
3152
|
const memory = {
|
|
2875
3153
|
id,
|
|
@@ -2913,11 +3191,17 @@ function mapNoteToMemory(input) {
|
|
|
2913
3191
|
aliases: asStringArray(ns["aliases"]),
|
|
2914
3192
|
entities: asStringArray(ns["entities"]),
|
|
2915
3193
|
updated_at: asString(ns["updated_at"]) ?? asString(frontmatter?.["updated_at"]),
|
|
3194
|
+
// Carried onto the memory as well as onto the claim below, because a note
|
|
3195
|
+
// that states no claim gets no claim object at all — and `remember` has
|
|
3196
|
+
// never written a `claim:` key, so that is most of a real brain. Written
|
|
3197
|
+
// only when present, so a note without one maps byte-identically to what
|
|
3198
|
+
// it always did.
|
|
3199
|
+
...asString(ns["valid_until"]) === null ? {} : { valid_until: asString(ns["valid_until"]) },
|
|
2916
3200
|
provenance: {
|
|
2917
3201
|
sourcePath: path2,
|
|
2918
3202
|
sourceRevision: revision,
|
|
2919
3203
|
mapping: explicit ? "explicit" : "heuristic",
|
|
2920
|
-
scopeSource:
|
|
3204
|
+
scopeSource: declaredScope2 !== null ? "explicit" : scope === null ? "none" : "path"
|
|
2921
3205
|
}
|
|
2922
3206
|
};
|
|
2923
3207
|
const claims = [];
|
|
@@ -2943,22 +3227,6 @@ function mapNoteToMemory(input) {
|
|
|
2943
3227
|
issues
|
|
2944
3228
|
};
|
|
2945
3229
|
}
|
|
2946
|
-
function isUntouchedBase(path2, content) {
|
|
2947
|
-
if (!/^01_BASE\//.test(path2)) return false;
|
|
2948
|
-
const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/, "");
|
|
2949
|
-
const lines = body.split(/\r?\n/).map((l) => l.trimEnd());
|
|
2950
|
-
let seenHeading = false;
|
|
2951
|
-
for (const line of lines) {
|
|
2952
|
-
if (line.trim() === "") continue;
|
|
2953
|
-
if (!seenHeading && line.startsWith("# ")) {
|
|
2954
|
-
seenHeading = true;
|
|
2955
|
-
continue;
|
|
2956
|
-
}
|
|
2957
|
-
if (/^-\s*$/.test(line)) continue;
|
|
2958
|
-
if (/^[-*+]\s+\S/.test(line) || /^#{1,6}\s/.test(line)) return false;
|
|
2959
|
-
}
|
|
2960
|
-
return seenHeading;
|
|
2961
|
-
}
|
|
2962
3230
|
var NAMESPACE, isRecord, asString, asStringArray, AUTHORABLE;
|
|
2963
3231
|
var init_markdown_mapping = __esm({
|
|
2964
3232
|
"../../packages/memory-engine/src/markdown-mapping.ts"() {
|
|
@@ -3048,7 +3316,7 @@ var init_cache_io = __esm({
|
|
|
3048
3316
|
"../../packages/memory-engine/src/view/cache-io.ts"() {
|
|
3049
3317
|
"use strict";
|
|
3050
3318
|
init_schema();
|
|
3051
|
-
SCHEMA_VERSION =
|
|
3319
|
+
SCHEMA_VERSION = 7;
|
|
3052
3320
|
POLICY_VERSION = 1;
|
|
3053
3321
|
CacheEntrySchema = z23.object({
|
|
3054
3322
|
path: z23.string().min(1),
|
|
@@ -3663,6 +3931,12 @@ function terms(text) {
|
|
|
3663
3931
|
(match) => splitMixedScript(match[0])
|
|
3664
3932
|
);
|
|
3665
3933
|
}
|
|
3934
|
+
function contentTerms(text) {
|
|
3935
|
+
const all = terms(text);
|
|
3936
|
+
if (all.some((t) => HANGUL.test(t))) return all;
|
|
3937
|
+
const content = all.filter((t) => !LATIN_STOPWORDS.has(t));
|
|
3938
|
+
return content.length > 0 ? content : all;
|
|
3939
|
+
}
|
|
3666
3940
|
function indexTerms(text) {
|
|
3667
3941
|
return [...text.normalize("NFC").toLowerCase().matchAll(TOKEN)].flatMap((match) => {
|
|
3668
3942
|
const token = match[0];
|
|
@@ -3735,8 +4009,9 @@ function fieldScore(queryTerms, field, weight, allowLeadingOneSyllable = false)
|
|
|
3735
4009
|
return score;
|
|
3736
4010
|
}
|
|
3737
4011
|
function searchMemories(memories, query) {
|
|
3738
|
-
const q =
|
|
4012
|
+
const q = contentTerms(query);
|
|
3739
4013
|
if (q.length === 0) return [];
|
|
4014
|
+
const scale = terms(query).length;
|
|
3740
4015
|
const scored = [];
|
|
3741
4016
|
for (const memory of memories) {
|
|
3742
4017
|
let score = fieldScore(q, memory.title, WEIGHTS.title, true);
|
|
@@ -3744,13 +4019,13 @@ function searchMemories(memories, query) {
|
|
|
3744
4019
|
for (const entity of memory.entities) score += fieldScore(q, entity, WEIGHTS.entity);
|
|
3745
4020
|
score += fieldScore(q, memory.summary, WEIGHTS.summary);
|
|
3746
4021
|
score += fieldScore(q, memory.scope, WEIGHTS.scope);
|
|
3747
|
-
if (score > 0) scored.push({ memory, score: score /
|
|
4022
|
+
if (score > 0) scored.push({ memory, score: score / scale });
|
|
3748
4023
|
}
|
|
3749
4024
|
return scored.sort(
|
|
3750
4025
|
(a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1
|
|
3751
4026
|
);
|
|
3752
4027
|
}
|
|
3753
|
-
var WEIGHTS, TOKEN, COMPONENT_SEPARATOR, HAS_COMPONENT_SEPARATOR, LATIN_HANGUL_BOUNDARY, SUBSTRING_MIN, PARTICLES, HANGUL, ONE_SYLLABLE_TOPIC_TERMS;
|
|
4028
|
+
var WEIGHTS, TOKEN, COMPONENT_SEPARATOR, HAS_COMPONENT_SEPARATOR, LATIN_HANGUL_BOUNDARY, LATIN_STOPWORDS, SUBSTRING_MIN, PARTICLES, HANGUL, ONE_SYLLABLE_TOPIC_TERMS;
|
|
3754
4029
|
var init_search = __esm({
|
|
3755
4030
|
"../../packages/memory-engine/src/recall/search.ts"() {
|
|
3756
4031
|
"use strict";
|
|
@@ -3759,6 +4034,120 @@ var init_search = __esm({
|
|
|
3759
4034
|
COMPONENT_SEPARATOR = /[._/-]+/u;
|
|
3760
4035
|
HAS_COMPONENT_SEPARATOR = /[._/-]/u;
|
|
3761
4036
|
LATIN_HANGUL_BOUNDARY = new RegExp("(?<=\\p{Script=Latin})(?=\\p{Script=Hangul})|(?<=\\p{Script=Hangul})(?=\\p{Script=Latin})", "u");
|
|
4037
|
+
LATIN_STOPWORDS = /* @__PURE__ */ new Set([
|
|
4038
|
+
// pronouns and possessives
|
|
4039
|
+
"i",
|
|
4040
|
+
"me",
|
|
4041
|
+
"my",
|
|
4042
|
+
"mine",
|
|
4043
|
+
"myself",
|
|
4044
|
+
"we",
|
|
4045
|
+
"us",
|
|
4046
|
+
"our",
|
|
4047
|
+
"ours",
|
|
4048
|
+
"ourselves",
|
|
4049
|
+
"you",
|
|
4050
|
+
"your",
|
|
4051
|
+
"yours",
|
|
4052
|
+
"yourself",
|
|
4053
|
+
"yourselves",
|
|
4054
|
+
"he",
|
|
4055
|
+
"him",
|
|
4056
|
+
"his",
|
|
4057
|
+
"himself",
|
|
4058
|
+
"she",
|
|
4059
|
+
"her",
|
|
4060
|
+
"hers",
|
|
4061
|
+
"herself",
|
|
4062
|
+
"it",
|
|
4063
|
+
"its",
|
|
4064
|
+
"itself",
|
|
4065
|
+
"they",
|
|
4066
|
+
"them",
|
|
4067
|
+
"their",
|
|
4068
|
+
"theirs",
|
|
4069
|
+
"themselves",
|
|
4070
|
+
// determiners
|
|
4071
|
+
"a",
|
|
4072
|
+
"an",
|
|
4073
|
+
"the",
|
|
4074
|
+
"this",
|
|
4075
|
+
"that",
|
|
4076
|
+
"these",
|
|
4077
|
+
"those",
|
|
4078
|
+
"some",
|
|
4079
|
+
"any",
|
|
4080
|
+
"each",
|
|
4081
|
+
"every",
|
|
4082
|
+
// auxiliaries and modals
|
|
4083
|
+
"am",
|
|
4084
|
+
"is",
|
|
4085
|
+
"are",
|
|
4086
|
+
"was",
|
|
4087
|
+
"were",
|
|
4088
|
+
"be",
|
|
4089
|
+
"been",
|
|
4090
|
+
"being",
|
|
4091
|
+
"do",
|
|
4092
|
+
"does",
|
|
4093
|
+
"did",
|
|
4094
|
+
"have",
|
|
4095
|
+
"has",
|
|
4096
|
+
"had",
|
|
4097
|
+
"will",
|
|
4098
|
+
"would",
|
|
4099
|
+
"shall",
|
|
4100
|
+
"should",
|
|
4101
|
+
"can",
|
|
4102
|
+
"could",
|
|
4103
|
+
"may",
|
|
4104
|
+
"might",
|
|
4105
|
+
"must",
|
|
4106
|
+
// question words
|
|
4107
|
+
"what",
|
|
4108
|
+
"which",
|
|
4109
|
+
"who",
|
|
4110
|
+
"whom",
|
|
4111
|
+
"whose",
|
|
4112
|
+
"when",
|
|
4113
|
+
"where",
|
|
4114
|
+
"why",
|
|
4115
|
+
"how",
|
|
4116
|
+
// prepositions
|
|
4117
|
+
"about",
|
|
4118
|
+
"above",
|
|
4119
|
+
"after",
|
|
4120
|
+
"at",
|
|
4121
|
+
"before",
|
|
4122
|
+
"by",
|
|
4123
|
+
"for",
|
|
4124
|
+
"from",
|
|
4125
|
+
"in",
|
|
4126
|
+
"into",
|
|
4127
|
+
"of",
|
|
4128
|
+
"off",
|
|
4129
|
+
"on",
|
|
4130
|
+
"onto",
|
|
4131
|
+
"over",
|
|
4132
|
+
"to",
|
|
4133
|
+
"under",
|
|
4134
|
+
"with",
|
|
4135
|
+
"without",
|
|
4136
|
+
"through",
|
|
4137
|
+
"during",
|
|
4138
|
+
// conjunctions and particles
|
|
4139
|
+
"and",
|
|
4140
|
+
"but",
|
|
4141
|
+
"or",
|
|
4142
|
+
"nor",
|
|
4143
|
+
"so",
|
|
4144
|
+
"if",
|
|
4145
|
+
"then",
|
|
4146
|
+
"than",
|
|
4147
|
+
"as",
|
|
4148
|
+
"there",
|
|
4149
|
+
"here"
|
|
4150
|
+
]);
|
|
3762
4151
|
SUBSTRING_MIN = 2;
|
|
3763
4152
|
PARTICLES = [
|
|
3764
4153
|
"\uC774\uB77C\uB3C4",
|
|
@@ -3909,7 +4298,7 @@ function freshnessOf(memory, claims, now) {
|
|
|
3909
4298
|
if (memory.status === "superseded") return "superseded";
|
|
3910
4299
|
if (memory.status === "retired") return "retired";
|
|
3911
4300
|
const current = claims.find((c2) => c2.id === memory.current_claim) ?? claims[0];
|
|
3912
|
-
const until = current?.valid_until;
|
|
4301
|
+
const until = current?.valid_until ?? memory.valid_until ?? null;
|
|
3913
4302
|
if (until != null) {
|
|
3914
4303
|
const t = Date.parse(until);
|
|
3915
4304
|
if (Number.isFinite(t) && t < now.getTime()) return "expired";
|
|
@@ -3939,7 +4328,23 @@ function recall(entries, query, opts = {}) {
|
|
|
3939
4328
|
(e) => (e.chunks ?? []).map((c2) => ({ memoryId: e.memory.id, label: c2.label, text: c2.text }))
|
|
3940
4329
|
)
|
|
3941
4330
|
).search(queryShape.lexical);
|
|
3942
|
-
const
|
|
4331
|
+
const offeredSemantic = depth === "exact" ? [] : opts.semantic ?? [];
|
|
4332
|
+
const reachedByWords = /* @__PURE__ */ new Set([
|
|
4333
|
+
...cardHits.map((hit) => hit.memory.id),
|
|
4334
|
+
...chunkHits.map((hit) => hit.memoryId)
|
|
4335
|
+
]);
|
|
4336
|
+
const floor = opts.meaningFloor;
|
|
4337
|
+
const bestSimilarity = offeredSemantic.reduce(
|
|
4338
|
+
(best, hit) => Math.max(best, Math.min(1, Math.max(0, hit.similarity))),
|
|
4339
|
+
0
|
|
4340
|
+
);
|
|
4341
|
+
const semanticHits = floor === void 0 ? offeredSemantic : offeredSemantic.filter((hit) => {
|
|
4342
|
+
if (reachedByWords.has(hit.id)) return true;
|
|
4343
|
+
const s = Math.min(1, Math.max(0, hit.similarity));
|
|
4344
|
+
if (floor.absolute !== void 0 && s < floor.absolute) return false;
|
|
4345
|
+
if (floor.gap !== void 0 && bestSimilarity - s > floor.gap) return false;
|
|
4346
|
+
return true;
|
|
4347
|
+
});
|
|
3943
4348
|
const scoreById = /* @__PURE__ */ new Map();
|
|
3944
4349
|
for (const hit of cardHits) scoreById.set(hit.memory.id, hit.score);
|
|
3945
4350
|
for (const hit of chunkHits) {
|
|
@@ -3949,6 +4354,14 @@ function recall(entries, query, opts = {}) {
|
|
|
3949
4354
|
const similarity = Math.min(1, Math.max(0, hit.similarity));
|
|
3950
4355
|
scoreById.set(hit.id, (scoreById.get(hit.id) ?? 0) + similarity * SEMANTIC_WEIGHT);
|
|
3951
4356
|
}
|
|
4357
|
+
const wordIds = reachedByWords;
|
|
4358
|
+
const meaningIds = new Set(semanticHits.map((hit) => hit.id));
|
|
4359
|
+
const matchedOf = (id) => {
|
|
4360
|
+
if (opts.semantic === void 0 || depth === "exact") return void 0;
|
|
4361
|
+
const words = wordIds.has(id);
|
|
4362
|
+
const meaning = meaningIds.has(id);
|
|
4363
|
+
return words && meaning ? "both" : words ? "words" : meaning ? "meaning" : void 0;
|
|
4364
|
+
};
|
|
3952
4365
|
const memoryById = new Map(allMemories.map((m) => [m.id, m]));
|
|
3953
4366
|
const hits = [...scoreById.entries()].flatMap(([id, score]) => {
|
|
3954
4367
|
const memory = memoryById.get(id);
|
|
@@ -4049,7 +4462,11 @@ function recall(entries, query, opts = {}) {
|
|
|
4049
4462
|
type: memory.type,
|
|
4050
4463
|
// The handle IS the progressive part. No claim text here, by
|
|
4051
4464
|
// construction rather than by remembering to strip it.
|
|
4052
|
-
detail_handle: `recall://${memory.id}
|
|
4465
|
+
detail_handle: `recall://${memory.id}`,
|
|
4466
|
+
...(() => {
|
|
4467
|
+
const matched = matchedOf(memory.id);
|
|
4468
|
+
return matched === void 0 ? {} : { matched };
|
|
4469
|
+
})()
|
|
4053
4470
|
};
|
|
4054
4471
|
}
|
|
4055
4472
|
}
|
|
@@ -4080,7 +4497,7 @@ function resolveDetail(entries, handle) {
|
|
|
4080
4497
|
const entry = entries.find((e) => e.memory.id === id);
|
|
4081
4498
|
return entry ? { memory: entry.memory, claims: entry.claims, relations: entry.relations } : null;
|
|
4082
4499
|
}
|
|
4083
|
-
var RECALL_DEPTHS, RecallOptionError, SEMANTIC_CANDIDATES, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, DECISION_NOUNS, DECISION_CONTEXT, DECISION_FILLERS, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
4500
|
+
var MEANING_ONLY_NOTE, RECALL_DEPTHS, RecallOptionError, SEMANTIC_CANDIDATES, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, DECISION_NOUNS, DECISION_CONTEXT, DECISION_FILLERS, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
4084
4501
|
var init_planner = __esm({
|
|
4085
4502
|
"../../packages/memory-engine/src/recall/planner.ts"() {
|
|
4086
4503
|
"use strict";
|
|
@@ -4089,6 +4506,7 @@ var init_planner = __esm({
|
|
|
4089
4506
|
init_conflicts();
|
|
4090
4507
|
init_chunks();
|
|
4091
4508
|
init_search();
|
|
4509
|
+
MEANING_ONLY_NOTE = "matched by meaning only \u2014 none of the question\u2019s words are in this note";
|
|
4092
4510
|
RECALL_DEPTHS = ["exact", "standard"];
|
|
4093
4511
|
RecallOptionError = class extends Error {
|
|
4094
4512
|
hint;
|
|
@@ -4175,18 +4593,164 @@ var init_planner = __esm({
|
|
|
4175
4593
|
}
|
|
4176
4594
|
});
|
|
4177
4595
|
|
|
4596
|
+
// ../../packages/memory-engine/src/note-body.ts
|
|
4597
|
+
function bodyOf(content) {
|
|
4598
|
+
const stripped = content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
4599
|
+
const fence2 = FENCE.exec(stripped);
|
|
4600
|
+
return fence2 ? stripped.slice(fence2[0].length) : stripped;
|
|
4601
|
+
}
|
|
4602
|
+
var BOM, FENCE;
|
|
4603
|
+
var init_note_body = __esm({
|
|
4604
|
+
"../../packages/memory-engine/src/note-body.ts"() {
|
|
4605
|
+
"use strict";
|
|
4606
|
+
BOM = String.fromCharCode(65279);
|
|
4607
|
+
FENCE = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/;
|
|
4608
|
+
}
|
|
4609
|
+
});
|
|
4610
|
+
|
|
4611
|
+
// ../../packages/memory-engine/src/base-templates.ts
|
|
4612
|
+
function base(id, title, summary, body) {
|
|
4613
|
+
return [
|
|
4614
|
+
"---",
|
|
4615
|
+
"memgineering:",
|
|
4616
|
+
` id: ${id}`,
|
|
4617
|
+
` title: ${title}`,
|
|
4618
|
+
` summary: ${summary}`,
|
|
4619
|
+
" scope: base",
|
|
4620
|
+
"---",
|
|
4621
|
+
"",
|
|
4622
|
+
`# ${title}`,
|
|
4623
|
+
"",
|
|
4624
|
+
body,
|
|
4625
|
+
""
|
|
4626
|
+
].join("\n");
|
|
4627
|
+
}
|
|
4628
|
+
function ownEntry(record, key2) {
|
|
4629
|
+
return Object.hasOwn(record, key2) ? record[key2] : void 0;
|
|
4630
|
+
}
|
|
4631
|
+
function wordingsFor(path2) {
|
|
4632
|
+
const current = ownEntry(BASE_TEMPLATES, path2);
|
|
4633
|
+
const retired = ownEntry(RETIRED_BASE_TEMPLATES, path2) ?? [];
|
|
4634
|
+
return current === void 0 ? retired : [current, ...retired];
|
|
4635
|
+
}
|
|
4636
|
+
function isExactUntouchedBase(path2, content) {
|
|
4637
|
+
const saved = asSaved(content);
|
|
4638
|
+
return wordingsFor(path2).some((t) => asSaved(t) === saved);
|
|
4639
|
+
}
|
|
4640
|
+
function isTemplateBody(path2, content) {
|
|
4641
|
+
const lines = bodyLines(content).join("\n");
|
|
4642
|
+
return wordingsFor(path2).some((t) => bodyLines(t).join("\n") === lines);
|
|
4643
|
+
}
|
|
4644
|
+
function isUnwrittenBody(path2, content) {
|
|
4645
|
+
if (isTemplateBody(path2, content)) return true;
|
|
4646
|
+
const [first, ...rest] = bodyLines(content);
|
|
4647
|
+
if (first === void 0) return false;
|
|
4648
|
+
const headings = wordingsFor(path2).map((t) => bodyLines(t)[0]);
|
|
4649
|
+
return headings.includes(first) && rest.every((line) => /^-\s*$/.test(line));
|
|
4650
|
+
}
|
|
4651
|
+
var BASE_TEMPLATES, RETIRED_BASE_TEMPLATES, BOM2, asSaved, bodyLines;
|
|
4652
|
+
var init_base_templates = __esm({
|
|
4653
|
+
"../../packages/memory-engine/src/base-templates.ts"() {
|
|
4654
|
+
"use strict";
|
|
4655
|
+
init_note_body();
|
|
4656
|
+
BASE_TEMPLATES = {
|
|
4657
|
+
"01_BASE/CRITICAL_FACTS.md": base(
|
|
4658
|
+
"critical-facts",
|
|
4659
|
+
"Critical facts",
|
|
4660
|
+
"The few things that must never be wrong.",
|
|
4661
|
+
[
|
|
4662
|
+
"Keep this short enough to read every session. Anything that can be",
|
|
4663
|
+
"looked up when needed belongs in a note instead.",
|
|
4664
|
+
"",
|
|
4665
|
+
"- "
|
|
4666
|
+
].join("\n")
|
|
4667
|
+
),
|
|
4668
|
+
"01_BASE/USER.md": base(
|
|
4669
|
+
"user",
|
|
4670
|
+
"About me",
|
|
4671
|
+
"Who I am and what context I work in.",
|
|
4672
|
+
[
|
|
4673
|
+
"Background, role, what you are working on, what an agent should assume",
|
|
4674
|
+
"you already know.",
|
|
4675
|
+
"",
|
|
4676
|
+
"- "
|
|
4677
|
+
].join("\n")
|
|
4678
|
+
),
|
|
4679
|
+
"01_BASE/PREFERENCES.md": base(
|
|
4680
|
+
"preferences",
|
|
4681
|
+
"Preferences",
|
|
4682
|
+
"How I like answers, code and decisions handled.",
|
|
4683
|
+
["Style, verbosity, how much to check in, how you like code written.", "", "- "].join("\n")
|
|
4684
|
+
),
|
|
4685
|
+
"01_BASE/BOUNDARIES.md": base(
|
|
4686
|
+
"boundaries",
|
|
4687
|
+
"Boundaries",
|
|
4688
|
+
"What an agent must not do without asking me first.",
|
|
4689
|
+
[
|
|
4690
|
+
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
4691
|
+
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
4692
|
+
"that will be interpreted generously.",
|
|
4693
|
+
"",
|
|
4694
|
+
"Storage boundaries belong here too: topics you never want written into",
|
|
4695
|
+
"this brain \u2014 health, beliefs, relationships, anything you name. An",
|
|
4696
|
+
"agent checks this file before it records anything.",
|
|
4697
|
+
"",
|
|
4698
|
+
"- "
|
|
4699
|
+
].join("\n")
|
|
4700
|
+
),
|
|
4701
|
+
"01_BASE/TOOLING.md": base(
|
|
4702
|
+
"tooling",
|
|
4703
|
+
"Tooling",
|
|
4704
|
+
"The tools, machines and commands I actually use.",
|
|
4705
|
+
[
|
|
4706
|
+
"Editors, languages, deploy commands, where things run. Enough that an",
|
|
4707
|
+
"agent does not have to guess or discover it again each time.",
|
|
4708
|
+
"",
|
|
4709
|
+
"- "
|
|
4710
|
+
].join("\n")
|
|
4711
|
+
)
|
|
4712
|
+
};
|
|
4713
|
+
RETIRED_BASE_TEMPLATES = {
|
|
4714
|
+
// Retired 0.11.1, when the prompt grew a storage-boundary invitation.
|
|
4715
|
+
"01_BASE/BOUNDARIES.md": [
|
|
4716
|
+
[
|
|
4717
|
+
"---",
|
|
4718
|
+
"memgineering:",
|
|
4719
|
+
" id: boundaries",
|
|
4720
|
+
" title: Boundaries",
|
|
4721
|
+
" summary: What an agent must not do without asking me first.",
|
|
4722
|
+
" scope: base",
|
|
4723
|
+
"---",
|
|
4724
|
+
"",
|
|
4725
|
+
"# Boundaries",
|
|
4726
|
+
"",
|
|
4727
|
+
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
4728
|
+
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
4729
|
+
"that will be interpreted generously.",
|
|
4730
|
+
"",
|
|
4731
|
+
"- ",
|
|
4732
|
+
""
|
|
4733
|
+
].join("\n")
|
|
4734
|
+
]
|
|
4735
|
+
};
|
|
4736
|
+
BOM2 = String.fromCharCode(65279);
|
|
4737
|
+
asSaved = (text) => (text.startsWith(BOM2) ? text.slice(BOM2.length) : text).replace(/\r\n/g, "\n").trimEnd();
|
|
4738
|
+
bodyLines = (content) => bodyOf(content).split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
|
|
4739
|
+
}
|
|
4740
|
+
});
|
|
4741
|
+
|
|
4178
4742
|
// ../../packages/memory-engine/src/recall/semantic.ts
|
|
4179
|
-
function withoutUntouchedBase(hits, contentById) {
|
|
4743
|
+
function withoutUntouchedBase(hits, contentById, isTemplate = isExactUntouchedBase) {
|
|
4180
4744
|
return hits.filter((hit) => {
|
|
4181
4745
|
const note = contentById.get(hit.id);
|
|
4182
|
-
return note === void 0 ? true : !
|
|
4746
|
+
return note === void 0 ? true : !isTemplate(note.path, note.content);
|
|
4183
4747
|
});
|
|
4184
4748
|
}
|
|
4185
4749
|
var SEMANTIC_FIELDS;
|
|
4186
4750
|
var init_semantic = __esm({
|
|
4187
4751
|
"../../packages/memory-engine/src/recall/semantic.ts"() {
|
|
4188
4752
|
"use strict";
|
|
4189
|
-
|
|
4753
|
+
init_base_templates();
|
|
4190
4754
|
SEMANTIC_FIELDS = {
|
|
4191
4755
|
/** `used` or `skipped`. Always present on a server that has this at all. */
|
|
4192
4756
|
state: "semantic",
|
|
@@ -4200,6 +4764,103 @@ var init_semantic = __esm({
|
|
|
4200
4764
|
}
|
|
4201
4765
|
});
|
|
4202
4766
|
|
|
4767
|
+
// ../../packages/memory-engine/src/recall/unfilled.ts
|
|
4768
|
+
function markUnfilled(cards, contentById, slots, inConflict = /* @__PURE__ */ new Set()) {
|
|
4769
|
+
const kept = [];
|
|
4770
|
+
const withheld = [];
|
|
4771
|
+
let templates = 0;
|
|
4772
|
+
for (const card of cards) {
|
|
4773
|
+
const note = contentById.get(card.id);
|
|
4774
|
+
const unfilled = note !== void 0 && isExactUntouchedBase(note.path, note.content);
|
|
4775
|
+
if (unfilled && slots !== void 0 && !inConflict.has(card.id) && ++templates > slots) {
|
|
4776
|
+
withheld.push(card.id);
|
|
4777
|
+
continue;
|
|
4778
|
+
}
|
|
4779
|
+
kept.push({ ...card, unfilled });
|
|
4780
|
+
}
|
|
4781
|
+
return { cards: kept, withheld };
|
|
4782
|
+
}
|
|
4783
|
+
var UNFILLED_FIELDS;
|
|
4784
|
+
var init_unfilled = __esm({
|
|
4785
|
+
"../../packages/memory-engine/src/recall/unfilled.ts"() {
|
|
4786
|
+
"use strict";
|
|
4787
|
+
init_base_templates();
|
|
4788
|
+
UNFILLED_FIELDS = {
|
|
4789
|
+
/** On every card from a server that has this: still the starter file, nothing written in it. */
|
|
4790
|
+
card: "unfilled",
|
|
4791
|
+
/**
|
|
4792
|
+
* In the request: how many such cards the caller will take. Absent means no
|
|
4793
|
+
* cap, so a client older than the field gets the whole ranking it always got —
|
|
4794
|
+
* the same boundary `budget` draws: the client states the capability.
|
|
4795
|
+
*/
|
|
4796
|
+
slots: "template_slots",
|
|
4797
|
+
/** In the response, only when the cap left something out: how many. */
|
|
4798
|
+
withheld: "templates_withheld"
|
|
4799
|
+
};
|
|
4800
|
+
}
|
|
4801
|
+
});
|
|
4802
|
+
|
|
4803
|
+
// ../../packages/memory-engine/src/setup-progress.ts
|
|
4804
|
+
var SETUP_PROGRESS_CODES;
|
|
4805
|
+
var init_setup_progress = __esm({
|
|
4806
|
+
"../../packages/memory-engine/src/setup-progress.ts"() {
|
|
4807
|
+
"use strict";
|
|
4808
|
+
SETUP_PROGRESS_CODES = [
|
|
4809
|
+
/** Installing the guidance and settings. */
|
|
4810
|
+
"writing_config",
|
|
4811
|
+
/** Uploading the chosen folder's notes. */
|
|
4812
|
+
"uploading",
|
|
4813
|
+
/** An upload chosen on the screen, held until somebody at the terminal checks the account. */
|
|
4814
|
+
"upload_waits_for_check",
|
|
4815
|
+
/** Finished. */
|
|
4816
|
+
"done",
|
|
4817
|
+
/** This machine is signed in to a different account; nothing was changed. */
|
|
4818
|
+
"different_account",
|
|
4819
|
+
/** Failed for another reason; `detail` says which. */
|
|
4820
|
+
"failed"
|
|
4821
|
+
];
|
|
4822
|
+
}
|
|
4823
|
+
});
|
|
4824
|
+
|
|
4825
|
+
// ../../packages/memory-engine/src/recall/query-prefix.ts
|
|
4826
|
+
var QUERY_EMBEDDING_PREFIX;
|
|
4827
|
+
var init_query_prefix = __esm({
|
|
4828
|
+
"../../packages/memory-engine/src/recall/query-prefix.ts"() {
|
|
4829
|
+
"use strict";
|
|
4830
|
+
QUERY_EMBEDDING_PREFIX = "task: search result | query: ";
|
|
4831
|
+
}
|
|
4832
|
+
});
|
|
4833
|
+
|
|
4834
|
+
// ../../packages/memory-engine/src/recall/scope-spread.ts
|
|
4835
|
+
function declaredScope(memory) {
|
|
4836
|
+
return memory.provenance?.scopeSource === "explicit" ? memory.scope ?? null : null;
|
|
4837
|
+
}
|
|
4838
|
+
function scopeSpread(returned, brain) {
|
|
4839
|
+
const scoped = returned.filter((s) => typeof s === "string" && s !== "");
|
|
4840
|
+
const distinct = new Set(scoped);
|
|
4841
|
+
if (distinct.size !== 1) return null;
|
|
4842
|
+
const only = [...distinct][0];
|
|
4843
|
+
const others = /* @__PURE__ */ new Set();
|
|
4844
|
+
for (const scope of brain) if (scope && scope !== only) others.add(scope);
|
|
4845
|
+
if (others.size === 0) return null;
|
|
4846
|
+
const sorted = [...others].sort((a, b) => a.localeCompare(b));
|
|
4847
|
+
return {
|
|
4848
|
+
scope: only,
|
|
4849
|
+
cards: scoped.length,
|
|
4850
|
+
of: returned.length,
|
|
4851
|
+
elsewhere: sorted.slice(0, ELSEWHERE_SHOWN),
|
|
4852
|
+
elsewhere_total: sorted.length
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
var SCOPE_SPREAD_FIELD, ELSEWHERE_SHOWN;
|
|
4856
|
+
var init_scope_spread = __esm({
|
|
4857
|
+
"../../packages/memory-engine/src/recall/scope-spread.ts"() {
|
|
4858
|
+
"use strict";
|
|
4859
|
+
SCOPE_SPREAD_FIELD = "scope_spread";
|
|
4860
|
+
ELSEWHERE_SHOWN = 5;
|
|
4861
|
+
}
|
|
4862
|
+
});
|
|
4863
|
+
|
|
4203
4864
|
// ../../packages/memory-engine/src/proposal/schema.ts
|
|
4204
4865
|
import { z as z24 } from "zod";
|
|
4205
4866
|
function isLifecycleAction(action) {
|
|
@@ -4208,8 +4869,17 @@ function isLifecycleAction(action) {
|
|
|
4208
4869
|
function isClassifyAction(action) {
|
|
4209
4870
|
return action === "reclassify";
|
|
4210
4871
|
}
|
|
4872
|
+
function isBindingAction(action) {
|
|
4873
|
+
return action === "unbind";
|
|
4874
|
+
}
|
|
4875
|
+
function isScopeAction(action) {
|
|
4876
|
+
return action === "rescope";
|
|
4877
|
+
}
|
|
4878
|
+
function isValidityAction(action) {
|
|
4879
|
+
return action === "expire";
|
|
4880
|
+
}
|
|
4211
4881
|
function carriesNoClaim(action) {
|
|
4212
|
-
return isLifecycleAction(action) || isClassifyAction(action);
|
|
4882
|
+
return isLifecycleAction(action) || isClassifyAction(action) || isBindingAction(action) || isScopeAction(action) || isValidityAction(action);
|
|
4213
4883
|
}
|
|
4214
4884
|
function isAccepted(value) {
|
|
4215
4885
|
return typeof value === "object" && value !== null && value[ACCEPTED] === true;
|
|
@@ -4238,6 +4908,47 @@ var init_schema2 = __esm({
|
|
|
4238
4908
|
"retire",
|
|
4239
4909
|
"unretire",
|
|
4240
4910
|
"reclassify",
|
|
4911
|
+
/**
|
|
4912
|
+
* A memory stops binding. Nothing else about it changes.
|
|
4913
|
+
*
|
|
4914
|
+
* It exists because there was no way to do this without saying something
|
|
4915
|
+
* else. Taking a marking back meant `revise --claim`, which is a verb about
|
|
4916
|
+
* conclusions — so unbinding thirteen rules on a real hosted brain required
|
|
4917
|
+
* restating thirteen conclusions the author had not changed, from text the
|
|
4918
|
+
* CLI hands back TRUNCATED. The person doing it read the note bodies out of
|
|
4919
|
+
* the database to avoid corrupting them, which is not a thing a user can do.
|
|
4920
|
+
*
|
|
4921
|
+
* One direction on purpose. `unbind` that could also bind would be a name
|
|
4922
|
+
* that lies half the time; marking something as a rule is already reachable
|
|
4923
|
+
* (`remember --rule`, or a `revise` that is genuinely saying something new).
|
|
4924
|
+
*/
|
|
4925
|
+
"unbind",
|
|
4926
|
+
/**
|
|
4927
|
+
* Where a memory binds changes. Nothing it SAYS changes.
|
|
4928
|
+
*
|
|
4929
|
+
* `scope` is read by `declaredScopeOf` and decides which of the two rule
|
|
4930
|
+
* budgets a decision is drawn from, so it is the field that answers "does
|
|
4931
|
+
* this bind me here". Nothing could write it. The one live consequence:
|
|
4932
|
+
* folding a project-scoped rule into an unscoped one required superseding
|
|
4933
|
+
* it, which retires the loser — so the rule would have vanished everywhere
|
|
4934
|
+
* except the project it was scoped to.
|
|
4935
|
+
*
|
|
4936
|
+
* The value is a scope NAME, or the reserved word `global`. Never null: see
|
|
4937
|
+
* the refinement below for why "absent" and "widen" must not be the same
|
|
4938
|
+
* value on the wire.
|
|
4939
|
+
*/
|
|
4940
|
+
"rescope",
|
|
4941
|
+
/**
|
|
4942
|
+
* A memory gets an end date. It is not retired, and it still recalls.
|
|
4943
|
+
*
|
|
4944
|
+
* `freshnessOf` has always returned `expired` for a claim whose
|
|
4945
|
+
* `valid_until` has passed, and `CURRENCY_RANK` has always ranked that just
|
|
4946
|
+
* below `current` — read, ranked, and labelled, with nothing able to write
|
|
4947
|
+
* it. A date-stamped fact ("Phase 1 lands by the end of October") therefore
|
|
4948
|
+
* had two options, both wrong: retire it, which says it no longer holds, or
|
|
4949
|
+
* leave it, which says it holds forever.
|
|
4950
|
+
*/
|
|
4951
|
+
"expire",
|
|
4241
4952
|
"ignore"
|
|
4242
4953
|
]);
|
|
4243
4954
|
StorableActionSchema = ProposalActionSchema.exclude(["ignore"]);
|
|
@@ -4397,6 +5108,37 @@ var init_schema2 = __esm({
|
|
|
4397
5108
|
* reclassify is pending — approve, reject, or let it expire first.
|
|
4398
5109
|
*/
|
|
4399
5110
|
proposed_kind: MemoryKindSchema.nullable().default(null),
|
|
5111
|
+
/**
|
|
5112
|
+
* Where `rescope` moves the memory to, and null for everything else.
|
|
5113
|
+
*
|
|
5114
|
+
* NEVER null to mean "everywhere". That was the first design and it is the
|
|
5115
|
+
* one thing this field must not do: "the caller sent no scope" and "the
|
|
5116
|
+
* caller asked to widen this rule" would be the same bytes, so the schema
|
|
5117
|
+
* could not require a scope on a rescope — and a request that LOST the
|
|
5118
|
+
* field, an older client, a hand-built body, a proposal rebuilt from disk,
|
|
5119
|
+
* would silently widen what a rule binds instead of failing.
|
|
5120
|
+
*
|
|
5121
|
+
* `global` is the word instead, and it is not invented here: `GLOBAL_SCOPE`
|
|
5122
|
+
* is already the reserved value a rule write uses to say it holds
|
|
5123
|
+
* everywhere, and `remember` already stores it as no scope at all. So the
|
|
5124
|
+
* widening is spelled, the refinement below can demand it, and the patcher
|
|
5125
|
+
* still writes what the thirty global rules already on a real brain look
|
|
5126
|
+
* like — no key.
|
|
5127
|
+
*
|
|
5128
|
+
* COMPATIBILITY BOUNDARY, as for `proposed_kind`: a proposal file carrying
|
|
5129
|
+
* `action: "rescope"` does not parse on a CLI built before this, and
|
|
5130
|
+
* `loadProposal` throws rather than silently dropping it.
|
|
5131
|
+
*/
|
|
5132
|
+
proposed_scope: z24.string().min(1).max(SCOPE_LIMIT).nullable().default(null),
|
|
5133
|
+
/**
|
|
5134
|
+
* The end date `expire` writes onto the current claim, null elsewhere.
|
|
5135
|
+
*
|
|
5136
|
+
* A date rather than a status: `freshnessOf` reads `valid_until` off the
|
|
5137
|
+
* claim and returns `expired`, which recall already labels and ranks below
|
|
5138
|
+
* `current`. Nothing here decides what expiry MEANS — that was decided
|
|
5139
|
+
* years of code ago and only the writer was missing.
|
|
5140
|
+
*/
|
|
5141
|
+
proposed_valid_until: z24.string().min(1).nullable().default(null),
|
|
4400
5142
|
relations: ProposedRelationsSchema,
|
|
4401
5143
|
/** Why the AI thinks this change is right. Shown at approval time. */
|
|
4402
5144
|
reason: z24.string().min(1),
|
|
@@ -4433,7 +5175,7 @@ var init_schema2 = __esm({
|
|
|
4433
5175
|
path: ["source_excerpt"]
|
|
4434
5176
|
}
|
|
4435
5177
|
).refine((p) => carriesNoClaim(p.action) || p.claim !== null, {
|
|
4436
|
-
message: "only retire, unretire and
|
|
5178
|
+
message: "only retire, unretire, reclassify, unbind, rescope and expire may omit the claim",
|
|
4437
5179
|
path: ["claim"]
|
|
4438
5180
|
}).refine((p) => isClassifyAction(p.action) === (p.proposed_kind !== null), {
|
|
4439
5181
|
message: "proposed_kind belongs to reclassify, and reclassify requires one",
|
|
@@ -4441,6 +5183,18 @@ var init_schema2 = __esm({
|
|
|
4441
5183
|
}).refine((p) => !isClassifyAction(p.action) || p.claim === null, {
|
|
4442
5184
|
message: "reclassify says what sort of memory this is; it does not take a claim",
|
|
4443
5185
|
path: ["claim"]
|
|
5186
|
+
}).refine((p) => isScopeAction(p.action) === (p.proposed_scope !== null), {
|
|
5187
|
+
message: "proposed_scope belongs to rescope, and rescope requires one",
|
|
5188
|
+
path: ["proposed_scope"]
|
|
5189
|
+
}).refine((p) => isValidityAction(p.action) === (p.proposed_valid_until !== null), {
|
|
5190
|
+
message: "proposed_valid_until belongs to expire, and expire requires one",
|
|
5191
|
+
path: ["proposed_valid_until"]
|
|
5192
|
+
}).refine((p) => !isScopeAction(p.action) || p.claim === null, {
|
|
5193
|
+
message: "rescope says where a memory binds; it does not take a claim",
|
|
5194
|
+
path: ["claim"]
|
|
5195
|
+
}).refine((p) => !isValidityAction(p.action) || p.claim === null, {
|
|
5196
|
+
message: "expire says when a memory stops being current; it does not take a claim",
|
|
5197
|
+
path: ["claim"]
|
|
4444
5198
|
});
|
|
4445
5199
|
ACCEPTED = /* @__PURE__ */ Symbol("memgineering.approved");
|
|
4446
5200
|
}
|
|
@@ -4490,12 +5244,42 @@ function detectConsolidation(entries, opts = {}) {
|
|
|
4490
5244
|
...titles
|
|
4491
5245
|
];
|
|
4492
5246
|
const out = rankCandidates(scored);
|
|
5247
|
+
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
4493
5248
|
return {
|
|
4494
|
-
candidates: out
|
|
5249
|
+
candidates: shareTheList(out, limit),
|
|
5250
|
+
by_rule: countByRule(out),
|
|
4495
5251
|
scanned: budget.scanned,
|
|
4496
5252
|
truncated: budget.truncated
|
|
4497
5253
|
};
|
|
4498
5254
|
}
|
|
5255
|
+
function shareTheList(ranked, limit) {
|
|
5256
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
5257
|
+
for (const candidate of ranked) {
|
|
5258
|
+
const lane = lanes.get(candidate.rule);
|
|
5259
|
+
if (lane) lane.push(candidate);
|
|
5260
|
+
else lanes.set(candidate.rule, [candidate]);
|
|
5261
|
+
}
|
|
5262
|
+
const out = [];
|
|
5263
|
+
const queues = [...lanes.values()];
|
|
5264
|
+
let round = 0;
|
|
5265
|
+
for (; ; ) {
|
|
5266
|
+
let added = false;
|
|
5267
|
+
for (const queue of queues) {
|
|
5268
|
+
if (out.length >= limit) return out;
|
|
5269
|
+
const next = queue[round];
|
|
5270
|
+
if (next === void 0) continue;
|
|
5271
|
+
out.push(next);
|
|
5272
|
+
added = true;
|
|
5273
|
+
}
|
|
5274
|
+
if (!added) return out;
|
|
5275
|
+
round += 1;
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
function countByRule(ranked) {
|
|
5279
|
+
const out = {};
|
|
5280
|
+
for (const candidate of ranked) out[candidate.rule] = (out[candidate.rule] ?? 0) + 1;
|
|
5281
|
+
return out;
|
|
5282
|
+
}
|
|
4499
5283
|
function titleTerms(title) {
|
|
4500
5284
|
const out = /* @__PURE__ */ new Set();
|
|
4501
5285
|
for (const term of terms(title)) {
|
|
@@ -4843,19 +5627,38 @@ function kindPromotions(entries) {
|
|
|
4843
5627
|
const out = [];
|
|
4844
5628
|
for (const entry of entries) {
|
|
4845
5629
|
if (entry.memory.binding !== true) continue;
|
|
4846
|
-
|
|
5630
|
+
const kind = entry.memory.kind;
|
|
5631
|
+
if (kind !== void 0 && RULE_SHAPED_KINDS.has(kind)) continue;
|
|
5632
|
+
if (kind === void 0) {
|
|
5633
|
+
out.push({
|
|
5634
|
+
candidate: {
|
|
5635
|
+
rule: "binding_unclassified",
|
|
5636
|
+
memory_id: entry.memory.id,
|
|
5637
|
+
path: entry.path,
|
|
5638
|
+
title: entry.memory.title,
|
|
5639
|
+
other_id: null,
|
|
5640
|
+
other_path: null,
|
|
5641
|
+
other_title: null,
|
|
5642
|
+
evidence: "marked as binding and left unclassified \u2014 either a decision nobody classified, or something that was never a rule",
|
|
5643
|
+
suggested_action: "decide",
|
|
5644
|
+
suggested_kind: null
|
|
5645
|
+
},
|
|
5646
|
+
confidence: 4
|
|
5647
|
+
});
|
|
5648
|
+
continue;
|
|
5649
|
+
}
|
|
4847
5650
|
out.push({
|
|
4848
5651
|
candidate: {
|
|
4849
|
-
rule: "
|
|
5652
|
+
rule: "rule_demotion",
|
|
4850
5653
|
memory_id: entry.memory.id,
|
|
4851
5654
|
path: entry.path,
|
|
4852
5655
|
title: entry.memory.title,
|
|
4853
5656
|
other_id: null,
|
|
4854
5657
|
other_path: null,
|
|
4855
5658
|
other_title: null,
|
|
4856
|
-
evidence:
|
|
4857
|
-
suggested_action: "
|
|
4858
|
-
suggested_kind:
|
|
5659
|
+
evidence: `marked as binding but classified as ${kind} \u2014 the author said it is not a decision, so it may not be a rule`,
|
|
5660
|
+
suggested_action: "unmark_rule",
|
|
5661
|
+
suggested_kind: null
|
|
4859
5662
|
},
|
|
4860
5663
|
confidence: 4
|
|
4861
5664
|
});
|
|
@@ -4962,7 +5765,7 @@ function* pairsFrom(buckets, budget) {
|
|
|
4962
5765
|
}
|
|
4963
5766
|
}
|
|
4964
5767
|
}
|
|
4965
|
-
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, SEMANTIC_PAIR_BUDGET, TITLE_OVERLAP, SAME_SCOPE_SEMANTIC_SIMILARITY, SHARED_ENTITY_SEMANTIC_SIMILARITY, GLOBAL_SEMANTIC_SIMILARITY, RECIPROCAL_NEIGHBOURS, CandidateComponents;
|
|
5768
|
+
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, SEMANTIC_PAIR_BUDGET, TITLE_OVERLAP, SAME_SCOPE_SEMANTIC_SIMILARITY, SHARED_ENTITY_SEMANTIC_SIMILARITY, GLOBAL_SEMANTIC_SIMILARITY, RECIPROCAL_NEIGHBOURS, RULE_SHAPED_KINDS, CandidateComponents;
|
|
4966
5769
|
var init_detect = __esm({
|
|
4967
5770
|
"../../packages/memory-engine/src/proposal/detect.ts"() {
|
|
4968
5771
|
"use strict";
|
|
@@ -4973,7 +5776,9 @@ var init_detect = __esm({
|
|
|
4973
5776
|
"semantic_similarity",
|
|
4974
5777
|
"shared_context",
|
|
4975
5778
|
"similar_title",
|
|
4976
|
-
"kind_promotion"
|
|
5779
|
+
"kind_promotion",
|
|
5780
|
+
"rule_demotion",
|
|
5781
|
+
"binding_unclassified"
|
|
4977
5782
|
];
|
|
4978
5783
|
DEFAULT_LIMIT = 50;
|
|
4979
5784
|
DEFAULT_PAIR_BUDGET = 2e5;
|
|
@@ -4983,6 +5788,7 @@ var init_detect = __esm({
|
|
|
4983
5788
|
SHARED_ENTITY_SEMANTIC_SIMILARITY = 0.75;
|
|
4984
5789
|
GLOBAL_SEMANTIC_SIMILARITY = 0.72;
|
|
4985
5790
|
RECIPROCAL_NEIGHBOURS = 2;
|
|
5791
|
+
RULE_SHAPED_KINDS = /* @__PURE__ */ new Set(["decision", "preference"]);
|
|
4986
5792
|
CandidateComponents = class {
|
|
4987
5793
|
parent = /* @__PURE__ */ new Map();
|
|
4988
5794
|
root(id) {
|
|
@@ -5319,12 +6125,12 @@ function commit(prepared, input, opts = {}) {
|
|
|
5319
6125
|
if (carriesNoClaim(parsed.action)) {
|
|
5320
6126
|
if (parsed.claim !== null) {
|
|
5321
6127
|
throw Object.assign(new Error(`${parsed.action} does not take a claim`), {
|
|
5322
|
-
hint: isClassifyAction(parsed.action) ? `${parsed.action} says what sort of memory this is, not what it says. To change the conclusion too, use supersede.` : `${parsed.action} changes whether a memory is current, not what it says. To change the conclusion too, use supersede.`
|
|
6128
|
+
hint: isClassifyAction(parsed.action) ? `${parsed.action} says what sort of memory this is, not what it says. To change the conclusion too, use supersede.` : isBindingAction(parsed.action) ? `${parsed.action} takes a marking back and leaves the conclusion exactly as it is \u2014 that is the whole reason it exists. To change what the note says as well, use supersede.` : isScopeAction(parsed.action) ? `${parsed.action} says where a memory binds, not what it says. To change the conclusion too, use supersede.` : isValidityAction(parsed.action) ? `${parsed.action} gives a memory an end date and leaves the conclusion exactly as it is. To change what the note says as well, use supersede.` : `${parsed.action} changes whether a memory is current, not what it says. To change the conclusion too, use supersede.`
|
|
5323
6129
|
});
|
|
5324
6130
|
}
|
|
5325
6131
|
} else if (parsed.claim === null) {
|
|
5326
6132
|
throw Object.assign(new Error(`${parsed.action} needs a claim`), {
|
|
5327
|
-
hint: "Only retire, unretire and
|
|
6133
|
+
hint: "Only retire, unretire, reclassify, unbind, rescope and expire may omit it. They change a memory's standing, its classification, whether it binds, where it binds or when it stops being current \u2014 rather than its content."
|
|
5328
6134
|
});
|
|
5329
6135
|
} else if ((parsed.action === "supersede" || parsed.action === "create") && parsed.claim.summary === null) {
|
|
5330
6136
|
throw Object.assign(new Error(`${parsed.action} needs a summary as well as a claim`), {
|
|
@@ -5341,6 +6147,26 @@ function commit(prepared, input, opts = {}) {
|
|
|
5341
6147
|
hint: "Only `reclassify` moves a memory's kind, and it does nothing else. Propose it separately, with its own reason."
|
|
5342
6148
|
});
|
|
5343
6149
|
}
|
|
6150
|
+
if (isScopeAction(parsed.action) && parsed.proposed_scope === null) {
|
|
6151
|
+
throw Object.assign(new Error(`${parsed.action} needs the scope it is proposing`), {
|
|
6152
|
+
hint: `Pass a project name, or \`${GLOBAL_SCOPE}\` to say the memory holds everywhere. Widening is spelled out because it cannot be taken back by accident.`
|
|
6153
|
+
});
|
|
6154
|
+
}
|
|
6155
|
+
if (!isScopeAction(parsed.action) && parsed.proposed_scope !== null) {
|
|
6156
|
+
throw Object.assign(new Error(`${parsed.action} does not set the scope`), {
|
|
6157
|
+
hint: "Only `rescope` moves where a memory binds, and it does nothing else. Propose it separately, with its own reason."
|
|
6158
|
+
});
|
|
6159
|
+
}
|
|
6160
|
+
if (isValidityAction(parsed.action) && parsed.proposed_valid_until === null) {
|
|
6161
|
+
throw Object.assign(new Error(`${parsed.action} needs the date it is proposing`), {
|
|
6162
|
+
hint: "Pass an ISO date \u2014 the day the memory stops being current. It is not retired by this: recall still returns it, ranked below what is current and labelled."
|
|
6163
|
+
});
|
|
6164
|
+
}
|
|
6165
|
+
if (!isValidityAction(parsed.action) && parsed.proposed_valid_until !== null) {
|
|
6166
|
+
throw Object.assign(new Error(`${parsed.action} does not set an end date`), {
|
|
6167
|
+
hint: "Only `expire` gives a memory an end date, and it does nothing else. Propose it separately, with its own reason."
|
|
6168
|
+
});
|
|
6169
|
+
}
|
|
5344
6170
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
5345
6171
|
const at = now.toISOString();
|
|
5346
6172
|
const draft = {
|
|
@@ -5350,6 +6176,8 @@ function commit(prepared, input, opts = {}) {
|
|
|
5350
6176
|
memory_id: parsed.memory_id,
|
|
5351
6177
|
claim: parsed.claim,
|
|
5352
6178
|
proposed_kind: parsed.proposed_kind,
|
|
6179
|
+
proposed_scope: parsed.proposed_scope,
|
|
6180
|
+
proposed_valid_until: parsed.proposed_valid_until,
|
|
5353
6181
|
relations: parsed.relations,
|
|
5354
6182
|
reason: parsed.reason,
|
|
5355
6183
|
source_excerpt: parsed.source_excerpt,
|
|
@@ -5380,14 +6208,22 @@ var init_commit = __esm({
|
|
|
5380
6208
|
*/
|
|
5381
6209
|
memory_id: z26.string().min(1).nullable().default(null),
|
|
5382
6210
|
/**
|
|
5383
|
-
* Omitted by `retire` / `unretire` / `reclassify`, which change a
|
|
5384
|
-
* standing
|
|
5385
|
-
* the other action rules so the error can
|
|
5386
|
-
* at a field.
|
|
6211
|
+
* Omitted by `retire` / `unretire` / `reclassify` / `unbind`, which change a
|
|
6212
|
+
* memory's standing, its classification or whether it binds — rather than
|
|
6213
|
+
* what it says. Checked below with the other action rules so the error can
|
|
6214
|
+
* name the action instead of pointing at a field.
|
|
5387
6215
|
*/
|
|
5388
6216
|
claim: ProposedClaimSchema.nullable().default(null),
|
|
5389
6217
|
/** What `reclassify` is reclassifying this memory AS. Only that action takes one. */
|
|
5390
6218
|
proposed_kind: MemoryKindSchema.nullable().default(null),
|
|
6219
|
+
/**
|
|
6220
|
+
* Where `rescope` moves this memory to — a scope name, or the reserved word
|
|
6221
|
+
* `global`. Only that action takes one, and it may not be null: see
|
|
6222
|
+
* `ProposalSchema.proposed_scope` for why "absent" must not spell "widen".
|
|
6223
|
+
*/
|
|
6224
|
+
proposed_scope: z26.string().min(1).max(SCOPE_LIMIT).nullable().default(null),
|
|
6225
|
+
/** The end date `expire` writes onto the current claim. Only that action takes one. */
|
|
6226
|
+
proposed_valid_until: z26.string().min(1).nullable().default(null),
|
|
5391
6227
|
relations: ProposedRelationsSchema.default({}),
|
|
5392
6228
|
reason: z26.string().min(1),
|
|
5393
6229
|
/** The passage from the note that justifies this. Required to submit. */
|
|
@@ -5488,6 +6324,68 @@ function reclassifyInput(entry, kind, opts = {}) {
|
|
|
5488
6324
|
target_path: entry.path
|
|
5489
6325
|
};
|
|
5490
6326
|
}
|
|
6327
|
+
function unbindInput(entry, opts = {}) {
|
|
6328
|
+
return {
|
|
6329
|
+
action: "unbind",
|
|
6330
|
+
memory_id: entry.memory.id,
|
|
6331
|
+
claim: null,
|
|
6332
|
+
reason: opts.reason ?? "no longer binding",
|
|
6333
|
+
source_excerpt: opts.because ?? entry.memory.summary ?? entry.memory.title,
|
|
6334
|
+
target_path: entry.path
|
|
6335
|
+
};
|
|
6336
|
+
}
|
|
6337
|
+
function rescopeInput(entry, scope, opts = {}) {
|
|
6338
|
+
const wanted = scope.trim();
|
|
6339
|
+
if (wanted === "") {
|
|
6340
|
+
throw Object.assign(new Error("a scope cannot be empty"), {
|
|
6341
|
+
hint: `Name the project this memory binds in, or \`${GLOBAL_SCOPE}\` to say it holds everywhere.`
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
if ([...wanted].length > SCOPE_LIMIT) {
|
|
6345
|
+
throw Object.assign(new Error(`a scope may be at most ${SCOPE_LIMIT} characters`), {
|
|
6346
|
+
hint: "A scope names a project. If it needs a sentence, it belongs in the note."
|
|
6347
|
+
});
|
|
6348
|
+
}
|
|
6349
|
+
const declared = declaredScopeOf(entry.memory);
|
|
6350
|
+
const same = isGlobalScope(wanted) ? declared === null : declared === wanted;
|
|
6351
|
+
if (same) {
|
|
6352
|
+
throw Object.assign(new Error(`${entry.memory.title} already binds there`), {
|
|
6353
|
+
hint: "Nothing was written, and nothing needed to be."
|
|
6354
|
+
});
|
|
6355
|
+
}
|
|
6356
|
+
return {
|
|
6357
|
+
action: "rescope",
|
|
6358
|
+
memory_id: entry.memory.id,
|
|
6359
|
+
claim: null,
|
|
6360
|
+
proposed_scope: wanted,
|
|
6361
|
+
reason: opts.reason ?? `binds in ${wanted}`,
|
|
6362
|
+
source_excerpt: opts.because ?? entry.memory.summary ?? entry.memory.title,
|
|
6363
|
+
target_path: entry.path
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6366
|
+
function expireInput(entry, until, opts = {}) {
|
|
6367
|
+
const wanted = until.trim();
|
|
6368
|
+
const at = Date.parse(wanted);
|
|
6369
|
+
if (!Number.isFinite(at)) {
|
|
6370
|
+
throw Object.assign(new Error(`${until} is not a date`), {
|
|
6371
|
+
hint: "Pass an ISO date, such as 2026-10-31 or 2026-10-31T00:00:00Z."
|
|
6372
|
+
});
|
|
6373
|
+
}
|
|
6374
|
+
if ((entry.claims[0]?.valid_until ?? entry.memory.valid_until ?? null) === wanted) {
|
|
6375
|
+
throw Object.assign(new Error(`${entry.memory.title} already ends then`), {
|
|
6376
|
+
hint: "Nothing was written, and nothing needed to be."
|
|
6377
|
+
});
|
|
6378
|
+
}
|
|
6379
|
+
return {
|
|
6380
|
+
action: "expire",
|
|
6381
|
+
memory_id: entry.memory.id,
|
|
6382
|
+
claim: null,
|
|
6383
|
+
proposed_valid_until: wanted,
|
|
6384
|
+
reason: opts.reason ?? `holds until ${wanted}`,
|
|
6385
|
+
source_excerpt: opts.because ?? entry.memory.summary ?? entry.memory.title,
|
|
6386
|
+
target_path: entry.path
|
|
6387
|
+
};
|
|
6388
|
+
}
|
|
5491
6389
|
function saysNothingNew(entry, input, normalize) {
|
|
5492
6390
|
const norm = (v) => normalize(v ?? "").trim();
|
|
5493
6391
|
const claim = input.claim;
|
|
@@ -5495,6 +6393,7 @@ function saysNothingNew(entry, input, normalize) {
|
|
|
5495
6393
|
if (norm(claim.text) !== norm(previousClaim(entry))) return false;
|
|
5496
6394
|
if (claim.summary != null && norm(claim.summary) !== norm(entry.memory.summary)) return false;
|
|
5497
6395
|
if (claim.title != null && norm(claim.title) !== norm(entry.memory.title)) return false;
|
|
6396
|
+
if (claim.binding != null && claim.binding !== (entry.memory.binding === true)) return false;
|
|
5498
6397
|
const declared = new Set(
|
|
5499
6398
|
entry.relations.filter((r) => r.type === "contradicts").map((r) => r.to)
|
|
5500
6399
|
);
|
|
@@ -5507,11 +6406,6 @@ function bodyStillAsserts(content, oldClaim, newClaim, normalize) {
|
|
|
5507
6406
|
if (line === normalize(newClaim?.split("\n")[0] ?? "").trim()) return null;
|
|
5508
6407
|
return bodyOf(content).includes(line) ? line : null;
|
|
5509
6408
|
}
|
|
5510
|
-
function bodyOf(content) {
|
|
5511
|
-
const stripped = content.replace(/^/, "");
|
|
5512
|
-
const fence2 = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/.exec(stripped);
|
|
5513
|
-
return fence2 ? stripped.slice(fence2[0].length) : stripped;
|
|
5514
|
-
}
|
|
5515
6409
|
function previousClaim(entry) {
|
|
5516
6410
|
const current = entry.claims.find((claim) => claim.id === entry.memory.current_claim) ?? entry.claims[0];
|
|
5517
6411
|
return current?.text ?? null;
|
|
@@ -5527,6 +6421,9 @@ var init_curate = __esm({
|
|
|
5527
6421
|
"../../packages/memory-engine/src/capture/curate.ts"() {
|
|
5528
6422
|
"use strict";
|
|
5529
6423
|
init_planner();
|
|
6424
|
+
init_schema();
|
|
6425
|
+
init_rule_budget();
|
|
6426
|
+
init_note_body();
|
|
5530
6427
|
NO_REASON_GIVEN = "revised directly";
|
|
5531
6428
|
noLifecycleReason = (action) => `${action}d directly`;
|
|
5532
6429
|
}
|
|
@@ -5577,6 +6474,25 @@ function desiredFields(proposal, existing) {
|
|
|
5577
6474
|
out["kind"] = proposal.proposed_kind;
|
|
5578
6475
|
return out;
|
|
5579
6476
|
}
|
|
6477
|
+
if (isBindingAction(proposal.action)) {
|
|
6478
|
+
delete out["binding"];
|
|
6479
|
+
return out;
|
|
6480
|
+
}
|
|
6481
|
+
if (isScopeAction(proposal.action)) {
|
|
6482
|
+
if (proposal.proposed_scope === null) {
|
|
6483
|
+
throw new Error(`${proposal.action} reached the patcher with no scope`);
|
|
6484
|
+
}
|
|
6485
|
+
if (isGlobalScope(proposal.proposed_scope)) delete out["scope"];
|
|
6486
|
+
else out["scope"] = proposal.proposed_scope;
|
|
6487
|
+
return out;
|
|
6488
|
+
}
|
|
6489
|
+
if (isValidityAction(proposal.action)) {
|
|
6490
|
+
if (proposal.proposed_valid_until === null) {
|
|
6491
|
+
throw new Error(`${proposal.action} reached the patcher with no date`);
|
|
6492
|
+
}
|
|
6493
|
+
out["valid_until"] = proposal.proposed_valid_until;
|
|
6494
|
+
return out;
|
|
6495
|
+
}
|
|
5580
6496
|
const claim = proposal.claim;
|
|
5581
6497
|
if (claim === null) {
|
|
5582
6498
|
throw new Error(`${proposal.action} reached the patcher with no claim`);
|
|
@@ -5874,6 +6790,108 @@ function verifyPatch(patched, proposal, before) {
|
|
|
5874
6790
|
}
|
|
5875
6791
|
return;
|
|
5876
6792
|
}
|
|
6793
|
+
if (isBindingAction(proposal.action)) {
|
|
6794
|
+
if (mapped.memory.binding === true) {
|
|
6795
|
+
throw new PatchError(
|
|
6796
|
+
"the patched note still reads as binding",
|
|
6797
|
+
"This is a defect in the patcher, not in your note. Nothing was written."
|
|
6798
|
+
);
|
|
6799
|
+
}
|
|
6800
|
+
if (before === void 0) {
|
|
6801
|
+
throw new PatchError(
|
|
6802
|
+
`${proposal.action} cannot be verified without the note it started from`,
|
|
6803
|
+
"This is a defect in the caller, not in your note. Nothing was written."
|
|
6804
|
+
);
|
|
6805
|
+
}
|
|
6806
|
+
const was = mapNoteToMemory({ path: proposal.target.path, content: before, revision: null });
|
|
6807
|
+
if (was.claims[0]?.text !== mapped.claims[0]?.text) {
|
|
6808
|
+
throw new PatchError(
|
|
6809
|
+
`${proposal.action} changed the conclusion, which it must never do`,
|
|
6810
|
+
"Taking a marking back leaves what the note says exactly as it was. Nothing was written."
|
|
6811
|
+
);
|
|
6812
|
+
}
|
|
6813
|
+
if (was.memory.summary !== mapped.memory.summary) {
|
|
6814
|
+
throw new PatchError(
|
|
6815
|
+
`${proposal.action} changed the summary, which it must never do`,
|
|
6816
|
+
"Taking a marking back leaves what the note says exactly as it was \u2014 and the summary is what recall matches on. Nothing was written."
|
|
6817
|
+
);
|
|
6818
|
+
}
|
|
6819
|
+
if (was.memory.title !== mapped.memory.title) {
|
|
6820
|
+
throw new PatchError(
|
|
6821
|
+
`${proposal.action} changed the title, which it must never do`,
|
|
6822
|
+
"Taking a marking back leaves what the note says exactly as it was. Nothing was written."
|
|
6823
|
+
);
|
|
6824
|
+
}
|
|
6825
|
+
if (was.memory.status !== mapped.memory.status) {
|
|
6826
|
+
throw new PatchError(
|
|
6827
|
+
`${proposal.action} changed the status, which it must never do`,
|
|
6828
|
+
"A memory that no longer binds is still current. To take it out of the answers as well, use retire. Nothing was written."
|
|
6829
|
+
);
|
|
6830
|
+
}
|
|
6831
|
+
return;
|
|
6832
|
+
}
|
|
6833
|
+
if (isScopeAction(proposal.action) || isValidityAction(proposal.action)) {
|
|
6834
|
+
if (before === void 0) {
|
|
6835
|
+
throw new PatchError(
|
|
6836
|
+
`${proposal.action} cannot be verified without the note it started from`,
|
|
6837
|
+
"This is a defect in the caller, not in your note. Nothing was written."
|
|
6838
|
+
);
|
|
6839
|
+
}
|
|
6840
|
+
if (isScopeAction(proposal.action)) {
|
|
6841
|
+
const want = proposal.proposed_scope;
|
|
6842
|
+
const got = declaredScopeOf(mapped.memory);
|
|
6843
|
+
const landed = isGlobalScope(want) ? got === null : got === want;
|
|
6844
|
+
if (!landed) {
|
|
6845
|
+
throw new PatchError(
|
|
6846
|
+
"the patched note does not declare the scope that was asked for",
|
|
6847
|
+
"This is a defect in the patcher, not in your note. Nothing was written."
|
|
6848
|
+
);
|
|
6849
|
+
}
|
|
6850
|
+
} else if (
|
|
6851
|
+
// Read back the way `freshnessOf` reads it: the claim's date when the
|
|
6852
|
+
// note states a claim, the note's own otherwise. Asserting only on the
|
|
6853
|
+
// claim would reject every note `remember` wrote, since it has never
|
|
6854
|
+
// written a `claim:` key.
|
|
6855
|
+
(mapped.claims[0]?.valid_until ?? mapped.memory.valid_until ?? null) !== proposal.proposed_valid_until
|
|
6856
|
+
) {
|
|
6857
|
+
throw new PatchError(
|
|
6858
|
+
"the patched note does not carry the end date that was asked for",
|
|
6859
|
+
"This is a defect in the patcher, not in your note. Nothing was written."
|
|
6860
|
+
);
|
|
6861
|
+
}
|
|
6862
|
+
const was = mapNoteToMemory({ path: proposal.target.path, content: before, revision: null });
|
|
6863
|
+
if (was.claims[0]?.text !== mapped.claims[0]?.text) {
|
|
6864
|
+
throw new PatchError(
|
|
6865
|
+
`${proposal.action} changed the conclusion, which it must never do`,
|
|
6866
|
+
"It changes one field and leaves what the note says exactly as it was. Nothing was written."
|
|
6867
|
+
);
|
|
6868
|
+
}
|
|
6869
|
+
if (was.memory.summary !== mapped.memory.summary) {
|
|
6870
|
+
throw new PatchError(
|
|
6871
|
+
`${proposal.action} changed the summary, which it must never do`,
|
|
6872
|
+
"The summary is what recall matches on, and this action does not touch what the note says. Nothing was written."
|
|
6873
|
+
);
|
|
6874
|
+
}
|
|
6875
|
+
if (was.memory.title !== mapped.memory.title) {
|
|
6876
|
+
throw new PatchError(
|
|
6877
|
+
`${proposal.action} changed the title, which it must never do`,
|
|
6878
|
+
"It changes one field and leaves what the note says exactly as it was. Nothing was written."
|
|
6879
|
+
);
|
|
6880
|
+
}
|
|
6881
|
+
if (was.memory.status !== mapped.memory.status) {
|
|
6882
|
+
throw new PatchError(
|
|
6883
|
+
`${proposal.action} changed the status, which it must never do`,
|
|
6884
|
+
"Moving where a memory binds, or saying when it stops being current, does not retire it. To take it out of the answers, use retire. Nothing was written."
|
|
6885
|
+
);
|
|
6886
|
+
}
|
|
6887
|
+
if (was.memory.binding !== mapped.memory.binding) {
|
|
6888
|
+
throw new PatchError(
|
|
6889
|
+
`${proposal.action} changed whether the memory binds, which it must never do`,
|
|
6890
|
+
"To take a marking back, use `revise --no-rule`. Nothing was written."
|
|
6891
|
+
);
|
|
6892
|
+
}
|
|
6893
|
+
return;
|
|
6894
|
+
}
|
|
5877
6895
|
const supersedes = new Set(
|
|
5878
6896
|
mapped.relations.filter((r) => r.type === "supersedes").map((r) => r.to)
|
|
5879
6897
|
);
|
|
@@ -5944,11 +6962,13 @@ function reversePatch(content, patch) {
|
|
|
5944
6962
|
}
|
|
5945
6963
|
return null;
|
|
5946
6964
|
}
|
|
5947
|
-
var PATCHABLE_KEYS, NEVER_WRITTEN_KEYS, STATUS_WRITING_ACTIONS, KIND_WRITING_ACTIONS, NAMESPACE2, FENCE_RE, LOOKS_FENCED_RE, PatchError, BLOCK_KEY_RE, ANY_INDENTED_KEY_RE;
|
|
6965
|
+
var PATCHABLE_KEYS, NEVER_WRITTEN_KEYS, STATUS_WRITING_ACTIONS, KIND_WRITING_ACTIONS, SCOPE_WRITING_ACTIONS, NAMESPACE2, FENCE_RE, LOOKS_FENCED_RE, PatchError, BLOCK_KEY_RE, ANY_INDENTED_KEY_RE;
|
|
5948
6966
|
var init_patch = __esm({
|
|
5949
6967
|
"../../packages/memory-engine/src/proposal/patch.ts"() {
|
|
5950
6968
|
"use strict";
|
|
5951
6969
|
init_markdown_mapping();
|
|
6970
|
+
init_rule_budget();
|
|
6971
|
+
init_schema();
|
|
5952
6972
|
init_schema2();
|
|
5953
6973
|
PATCHABLE_KEYS = [
|
|
5954
6974
|
// The conclusion itself.
|
|
@@ -5972,6 +6992,7 @@ var init_patch = __esm({
|
|
|
5972
6992
|
NEVER_WRITTEN_KEYS = ["id", "type", "scope", "aliases", "status", "kind"];
|
|
5973
6993
|
STATUS_WRITING_ACTIONS = ["retire", "unretire"];
|
|
5974
6994
|
KIND_WRITING_ACTIONS = ["reclassify"];
|
|
6995
|
+
SCOPE_WRITING_ACTIONS = ["rescope"];
|
|
5975
6996
|
NAMESPACE2 = "memgineering";
|
|
5976
6997
|
FENCE_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(\r?\n|$)/;
|
|
5977
6998
|
LOOKS_FENCED_RE = /^---[ \t]*\r?\n/;
|
|
@@ -6013,15 +7034,15 @@ async function applyProposal(adapter, accepted) {
|
|
|
6013
7034
|
const patch = computePatch(current.content, proposal);
|
|
6014
7035
|
verifyPatch(patch.content, proposal, current.content);
|
|
6015
7036
|
if (patch.content === current.content) {
|
|
6016
|
-
const
|
|
6017
|
-
if (
|
|
7037
|
+
const base2 = proposal.target.baseRevision;
|
|
7038
|
+
if (base2 === null || base2 === void 0) {
|
|
6018
7039
|
return {
|
|
6019
7040
|
ok: false,
|
|
6020
7041
|
reason: "revision_conflict",
|
|
6021
7042
|
proposal: needsRebase(proposal, current.revision)
|
|
6022
7043
|
};
|
|
6023
7044
|
}
|
|
6024
|
-
if (adapter.compareRevision(
|
|
7045
|
+
if (adapter.compareRevision(base2, current.revision) === "diverged") {
|
|
6025
7046
|
return {
|
|
6026
7047
|
ok: false,
|
|
6027
7048
|
reason: "revision_conflict",
|
|
@@ -6088,23 +7109,36 @@ function rank(entries, events, opts) {
|
|
|
6088
7109
|
const recalls = events.filter((e) => !AUTOMATIC.has(e.verb));
|
|
6089
7110
|
const here = opts.here ? recalls.filter((e) => opts.here(e)) : [];
|
|
6090
7111
|
const hereLabel = opts.hereLabel ?? "in this folder";
|
|
6091
|
-
const
|
|
6092
|
-
const
|
|
7112
|
+
const usageIn = (source) => {
|
|
7113
|
+
const usage = /* @__PURE__ */ new Map();
|
|
7114
|
+
const at = (id) => {
|
|
7115
|
+
let u = usage.get(id);
|
|
7116
|
+
if (!u) usage.set(id, u = { opened: 0, surfaced: 0, credit: 0 });
|
|
7117
|
+
return u;
|
|
7118
|
+
};
|
|
6093
7119
|
for (const event of source) {
|
|
6094
|
-
|
|
6095
|
-
|
|
7120
|
+
if (event.verb === "recall") {
|
|
7121
|
+
event.returned_ids.forEach((id, i) => {
|
|
7122
|
+
const u = at(id);
|
|
7123
|
+
u.surfaced += 1;
|
|
7124
|
+
if (i === 0) u.credit += SURFACED_SHARE;
|
|
7125
|
+
});
|
|
7126
|
+
} else if (DELIBERATE.has(event.verb)) {
|
|
7127
|
+
const ids = event.opened_id ? [event.opened_id] : event.returned_ids;
|
|
7128
|
+
for (const id of new Set(ids)) at(id).opened += 1;
|
|
7129
|
+
}
|
|
6096
7130
|
}
|
|
6097
|
-
return
|
|
7131
|
+
return usage;
|
|
6098
7132
|
};
|
|
6099
|
-
const
|
|
6100
|
-
const
|
|
7133
|
+
const evidence = (u) => u ? u.opened + u.credit : 0;
|
|
7134
|
+
const hereUsage = usageIn(here);
|
|
7135
|
+
const anyUsage = usageIn(recalls);
|
|
6101
7136
|
const lastTouched = /* @__PURE__ */ new Map();
|
|
6102
7137
|
for (const event of recalls) {
|
|
6103
7138
|
const t = Date.parse(event.ts);
|
|
6104
7139
|
if (!Number.isFinite(t)) continue;
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
}
|
|
7140
|
+
const touched = event.verb === "recall" ? event.returned_ids.slice(0, 1) : DELIBERATE.has(event.verb) ? [event.opened_id ?? "", ...event.returned_ids].filter(Boolean) : [];
|
|
7141
|
+
for (const id of touched) lastTouched.set(id, Math.max(lastTouched.get(id) ?? 0, t));
|
|
6108
7142
|
}
|
|
6109
7143
|
const hasBase = entries.some((e) => e.path.startsWith("01_BASE/"));
|
|
6110
7144
|
const scored = [];
|
|
@@ -6114,16 +7148,30 @@ function rank(entries, events, opts) {
|
|
|
6114
7148
|
if (freshness === "retired") continue;
|
|
6115
7149
|
const why = [];
|
|
6116
7150
|
let score = 0;
|
|
6117
|
-
const
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
7151
|
+
const hereU = hereUsage.get(id);
|
|
7152
|
+
const anyU = anyUsage.get(id);
|
|
7153
|
+
const hereEvidence = evidence(hereU);
|
|
7154
|
+
const elsewhereEvidence = evidence(anyU) - hereEvidence;
|
|
7155
|
+
if (hereEvidence > 0) {
|
|
7156
|
+
score += WEIGHT.contextRecall * Math.log2(1 + hereEvidence);
|
|
7157
|
+
why.push(usageLine(hereU, hereLabel));
|
|
7158
|
+
}
|
|
7159
|
+
if (elsewhereEvidence > 1e-9) {
|
|
7160
|
+
score += WEIGHT.anyRecall * Math.log2(1 + elsewhereEvidence);
|
|
7161
|
+
if (hereEvidence === 0) {
|
|
7162
|
+
why.push(
|
|
7163
|
+
usageLine(
|
|
7164
|
+
{
|
|
7165
|
+
opened: anyU.opened - (hereU?.opened ?? 0),
|
|
7166
|
+
surfaced: anyU.surfaced - (hereU?.surfaced ?? 0)
|
|
7167
|
+
},
|
|
7168
|
+
"elsewhere"
|
|
7169
|
+
)
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
6126
7172
|
}
|
|
7173
|
+
const hereCount = (hereU?.opened ?? 0) + (hereU?.surfaced ?? 0);
|
|
7174
|
+
const anyCount = (anyU?.opened ?? 0) + (anyU?.surfaced ?? 0) - hereCount;
|
|
6127
7175
|
const touched = lastTouched.get(id);
|
|
6128
7176
|
if (touched !== void 0) {
|
|
6129
7177
|
const days = (opts.now.getTime() - touched) / 864e5;
|
|
@@ -6144,16 +7192,28 @@ function rank(entries, events, opts) {
|
|
|
6144
7192
|
}
|
|
6145
7193
|
if (score === 0 && !entry.path.startsWith("01_BASE/")) {
|
|
6146
7194
|
score += WEIGHT.neverReached;
|
|
6147
|
-
why.push(
|
|
7195
|
+
why.push(
|
|
7196
|
+
anyU && anyU.surfaced > 0 ? usageLine(
|
|
7197
|
+
{ opened: 0, surfaced: anyU.surfaced },
|
|
7198
|
+
// Labelled by where it happened: all here, none here, or both —
|
|
7199
|
+
// a total said "in this project" would claim the whole count.
|
|
7200
|
+
!hereU?.surfaced ? "elsewhere" : hereU.surfaced === anyU.surfaced ? hereLabel : `(${hereU.surfaced}\xD7 ${hereLabel})`
|
|
7201
|
+
) : "never surfaced yet"
|
|
7202
|
+
);
|
|
6148
7203
|
}
|
|
6149
7204
|
if (freshness !== "current") why.push(freshness);
|
|
6150
|
-
if (score > 0) scored.push({ entry, score, why });
|
|
7205
|
+
if (score > 0) scored.push({ entry, score, why, used: hereCount > 0 || anyCount > 0 });
|
|
6151
7206
|
}
|
|
6152
7207
|
return scored.sort(
|
|
6153
7208
|
(a, b) => b.score - a.score || a.entry.memory.id.localeCompare(b.entry.memory.id)
|
|
6154
7209
|
);
|
|
6155
7210
|
}
|
|
6156
|
-
|
|
7211
|
+
function usageLine(u, where) {
|
|
7212
|
+
if (u.opened === 0) return `surfaced ${u.surfaced}\xD7 ${where}, never opened`;
|
|
7213
|
+
if (u.surfaced === 0) return `opened ${u.opened}\xD7 ${where}`;
|
|
7214
|
+
return `opened ${u.opened}\xD7 \xB7 surfaced ${u.surfaced}\xD7 ${where}`;
|
|
7215
|
+
}
|
|
7216
|
+
var WEIGHT, STALE_BASE_DAYS, SURFACED_SHARE, DELIBERATE;
|
|
6157
7217
|
var init_rank = __esm({
|
|
6158
7218
|
"../../packages/memory-engine/src/resurface/rank.ts"() {
|
|
6159
7219
|
"use strict";
|
|
@@ -6206,26 +7266,35 @@ var init_rank = __esm({
|
|
|
6206
7266
|
binding: 5
|
|
6207
7267
|
};
|
|
6208
7268
|
STALE_BASE_DAYS = 30;
|
|
7269
|
+
SURFACED_SHARE = 0.5;
|
|
7270
|
+
DELIBERATE = /* @__PURE__ */ new Set(["open", "publish", "adapt", "read-public"]);
|
|
6209
7271
|
}
|
|
6210
7272
|
});
|
|
6211
7273
|
|
|
6212
7274
|
// ../../packages/memory-engine/src/index.ts
|
|
6213
7275
|
var src_exports2 = {};
|
|
6214
7276
|
__export(src_exports2, {
|
|
7277
|
+
BASE_TEMPLATES: () => BASE_TEMPLATES,
|
|
6215
7278
|
CONSOLIDATION_RULES: () => CONSOLIDATION_RULES,
|
|
6216
7279
|
CaptureCandidateSchema: () => CaptureCandidateSchema,
|
|
6217
7280
|
ClaimConfidenceSchema: () => ClaimConfidenceSchema,
|
|
6218
7281
|
ClaimSchema: () => ClaimSchema,
|
|
6219
7282
|
CommitInputSchema: () => CommitInputSchema,
|
|
7283
|
+
DEFAULT_RULE_BUDGET: () => DEFAULT_RULE_BUDGET,
|
|
6220
7284
|
DEFAULT_TTL_DAYS: () => DEFAULT_TTL_DAYS,
|
|
7285
|
+
DERIVED_BLUEPRINT: () => DERIVED_BLUEPRINT,
|
|
7286
|
+
DERIVED_DIR: () => DERIVED_DIR,
|
|
7287
|
+
GLOBAL_SCOPE: () => GLOBAL_SCOPE,
|
|
6221
7288
|
IllegalTransitionError: () => IllegalTransitionError,
|
|
6222
7289
|
KIND_WRITING_ACTIONS: () => KIND_WRITING_ACTIONS,
|
|
7290
|
+
MEANING_ONLY_NOTE: () => MEANING_ONLY_NOTE,
|
|
6223
7291
|
MappingModeSchema: () => MappingModeSchema,
|
|
6224
7292
|
MemoryKindSchema: () => MemoryKindSchema,
|
|
6225
7293
|
MemorySchema: () => MemorySchema,
|
|
6226
7294
|
MemoryStatusSchema: () => MemoryStatusSchema,
|
|
6227
7295
|
NEVER_WRITTEN_KEYS: () => NEVER_WRITTEN_KEYS,
|
|
6228
7296
|
NO_REASON_GIVEN: () => NO_REASON_GIVEN,
|
|
7297
|
+
OBSERVATIONS_DIR: () => OBSERVATIONS_DIR,
|
|
6229
7298
|
PATCHABLE_KEYS: () => PATCHABLE_KEYS,
|
|
6230
7299
|
PatchError: () => PatchError,
|
|
6231
7300
|
ProposalActionSchema: () => ProposalActionSchema,
|
|
@@ -6234,14 +7303,23 @@ __export(src_exports2, {
|
|
|
6234
7303
|
ProposedClaimSchema: () => ProposedClaimSchema,
|
|
6235
7304
|
ProposedRelationsSchema: () => ProposedRelationsSchema,
|
|
6236
7305
|
ProvenanceSchema: () => ProvenanceSchema,
|
|
7306
|
+
QUERY_EMBEDDING_PREFIX: () => QUERY_EMBEDDING_PREFIX,
|
|
6237
7307
|
RECALL_DEPTHS: () => RECALL_DEPTHS,
|
|
7308
|
+
RETIRED_BASE_TEMPLATES: () => RETIRED_BASE_TEMPLATES,
|
|
7309
|
+
RULE_BLOCK_BUDGET: () => RULE_BLOCK_BUDGET,
|
|
7310
|
+
RULE_WIDTH_MAX: () => RULE_WIDTH_MAX,
|
|
7311
|
+
RULE_WIDTH_MIN: () => RULE_WIDTH_MIN,
|
|
7312
|
+
RULE_WIRE_WIDTH: () => RULE_WIRE_WIDTH,
|
|
6238
7313
|
RecallOptionError: () => RecallOptionError,
|
|
6239
7314
|
RelationSchema: () => RelationSchema,
|
|
6240
7315
|
RelationSourceSchema: () => RelationSourceSchema,
|
|
6241
7316
|
RelationTypeSchema: () => RelationTypeSchema,
|
|
6242
7317
|
SCOPE_LIMIT: () => SCOPE_LIMIT,
|
|
7318
|
+
SCOPE_SPREAD_FIELD: () => SCOPE_SPREAD_FIELD,
|
|
7319
|
+
SCOPE_WRITING_ACTIONS: () => SCOPE_WRITING_ACTIONS,
|
|
6243
7320
|
SEMANTIC_CANDIDATES: () => SEMANTIC_CANDIDATES,
|
|
6244
7321
|
SEMANTIC_FIELDS: () => SEMANTIC_FIELDS,
|
|
7322
|
+
SETUP_PROGRESS_CODES: () => SETUP_PROGRESS_CODES,
|
|
6245
7323
|
STATUS_FOR_ACTION: () => STATUS_FOR_ACTION,
|
|
6246
7324
|
STATUS_WRITING_ACTIONS: () => STATUS_WRITING_ACTIONS,
|
|
6247
7325
|
StorableActionSchema: () => StorableActionSchema,
|
|
@@ -6249,6 +7327,7 @@ __export(src_exports2, {
|
|
|
6249
7327
|
TELEMETRY_MESSAGE_LIMIT: () => TELEMETRY_MESSAGE_LIMIT,
|
|
6250
7328
|
TelemetryErrorPayloadSchema: () => TelemetryErrorPayloadSchema2,
|
|
6251
7329
|
TransitionSchema: () => TransitionSchema,
|
|
7330
|
+
UNFILLED_FIELDS: () => UNFILLED_FIELDS,
|
|
6252
7331
|
accept: () => accept,
|
|
6253
7332
|
addedLines: () => addedLines,
|
|
6254
7333
|
applyProposal: () => applyProposal,
|
|
@@ -6270,28 +7349,39 @@ __export(src_exports2, {
|
|
|
6270
7349
|
commit: () => commit,
|
|
6271
7350
|
computePatch: () => computePatch,
|
|
6272
7351
|
confidenceOf: () => confidenceOf,
|
|
7352
|
+
declaredScope: () => declaredScope,
|
|
7353
|
+
declaredScopeOf: () => declaredScopeOf,
|
|
6273
7354
|
defaultExpiry: () => defaultExpiry,
|
|
6274
7355
|
defaultStateRoot: () => defaultStateRoot,
|
|
6275
7356
|
deleteProposal: () => deleteProposal,
|
|
6276
7357
|
detectConsolidation: () => detectConsolidation,
|
|
6277
7358
|
expireIfDue: () => expireIfDue,
|
|
7359
|
+
expireInput: () => expireInput,
|
|
6278
7360
|
findConflicts: () => findConflicts,
|
|
6279
7361
|
freshnessOf: () => freshnessOf,
|
|
7362
|
+
isBindingAction: () => isBindingAction,
|
|
6280
7363
|
isClassifyAction: () => isClassifyAction,
|
|
7364
|
+
isExactUntouchedBase: () => isExactUntouchedBase,
|
|
6281
7365
|
isExpired: () => isExpired2,
|
|
7366
|
+
isGlobalScope: () => isGlobalScope,
|
|
6282
7367
|
isLifecycleAction: () => isLifecycleAction,
|
|
6283
7368
|
isRecallDepth: () => isRecallDepth,
|
|
7369
|
+
isScopeAction: () => isScopeAction,
|
|
7370
|
+
isTemplateBody: () => isTemplateBody,
|
|
6284
7371
|
isTerminal: () => isTerminal,
|
|
6285
|
-
|
|
7372
|
+
isUnwrittenBody: () => isUnwrittenBody,
|
|
7373
|
+
isValidityAction: () => isValidityAction,
|
|
6286
7374
|
lifecycleInput: () => lifecycleInput,
|
|
6287
7375
|
listProposals: () => listProposals,
|
|
6288
7376
|
loadProposal: () => loadProposal,
|
|
6289
7377
|
mapNoteToMemory: () => mapNoteToMemory,
|
|
7378
|
+
markUnfilled: () => markUnfilled,
|
|
6290
7379
|
matchBoundaryTerms: () => matchBoundaryTerms,
|
|
6291
7380
|
missingCounterparts: () => missingCounterparts,
|
|
6292
7381
|
needsRebase: () => needsRebase,
|
|
6293
7382
|
newProposalId: () => newProposalId,
|
|
6294
7383
|
noLifecycleReason: () => noLifecycleReason,
|
|
7384
|
+
ownEntry: () => ownEntry,
|
|
6295
7385
|
prepare: () => prepare,
|
|
6296
7386
|
prepareForMemory: () => prepareForMemory,
|
|
6297
7387
|
previewProposal: () => previewProposal,
|
|
@@ -6307,15 +7397,23 @@ __export(src_exports2, {
|
|
|
6307
7397
|
reconcile: () => reconcile,
|
|
6308
7398
|
redact: () => redact,
|
|
6309
7399
|
renderNewNote: () => renderNewNote,
|
|
7400
|
+
rescopeInput: () => rescopeInput,
|
|
6310
7401
|
resolveDetail: () => resolveDetail,
|
|
6311
7402
|
reversePatch: () => reversePatch,
|
|
6312
7403
|
reviseInput: () => reviseInput,
|
|
7404
|
+
ruleLane: () => ruleLane,
|
|
7405
|
+
rulePassage: () => rulePassage,
|
|
7406
|
+
ruleTextOf: () => ruleTextOf,
|
|
7407
|
+
ruleWidthFor: () => ruleWidthFor,
|
|
6313
7408
|
saveProposal: () => saveProposal,
|
|
6314
7409
|
saysNothingNew: () => saysNothingNew,
|
|
7410
|
+
scopeSpread: () => scopeSpread,
|
|
6315
7411
|
searchMemories: () => searchMemories,
|
|
6316
7412
|
semanticSourceFromVectors: () => semanticSourceFromVectors,
|
|
7413
|
+
splitRuleBudget: () => splitRuleBudget,
|
|
6317
7414
|
terms: () => terms,
|
|
6318
7415
|
transition: () => transition,
|
|
7416
|
+
unbindInput: () => unbindInput,
|
|
6319
7417
|
validateBoundaryTerm: () => validateBoundaryTerm,
|
|
6320
7418
|
variantsOf: () => variantsOf,
|
|
6321
7419
|
verifyPatch: () => verifyPatch,
|
|
@@ -6326,6 +7424,9 @@ var init_src3 = __esm({
|
|
|
6326
7424
|
"../../packages/memory-engine/src/index.ts"() {
|
|
6327
7425
|
"use strict";
|
|
6328
7426
|
init_schema();
|
|
7427
|
+
init_layout();
|
|
7428
|
+
init_rule_budget();
|
|
7429
|
+
init_rule_text();
|
|
6329
7430
|
init_boundary();
|
|
6330
7431
|
init_markdown_mapping();
|
|
6331
7432
|
init_build();
|
|
@@ -6333,6 +7434,11 @@ var init_src3 = __esm({
|
|
|
6333
7434
|
init_reconcile();
|
|
6334
7435
|
init_planner();
|
|
6335
7436
|
init_semantic();
|
|
7437
|
+
init_unfilled();
|
|
7438
|
+
init_setup_progress();
|
|
7439
|
+
init_query_prefix();
|
|
7440
|
+
init_base_templates();
|
|
7441
|
+
init_scope_spread();
|
|
6336
7442
|
init_conflicts();
|
|
6337
7443
|
init_search();
|
|
6338
7444
|
init_schema2();
|
|
@@ -6927,13 +8033,18 @@ async function useClient() {
|
|
|
6927
8033
|
} catch (err) {
|
|
6928
8034
|
const code = err.code;
|
|
6929
8035
|
if (code === "authorization_pending") {
|
|
6930
|
-
if (debug)
|
|
8036
|
+
if (debug)
|
|
8037
|
+
process.stderr.write(
|
|
8038
|
+
"[kordis] auto-finalize: authorization_pending (keeping pending)\n"
|
|
8039
|
+
);
|
|
6931
8040
|
} else {
|
|
6932
8041
|
await clearPending();
|
|
6933
8042
|
if (debug) {
|
|
6934
8043
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6935
|
-
process.stderr.write(
|
|
6936
|
-
`)
|
|
8044
|
+
process.stderr.write(
|
|
8045
|
+
`[kordis] auto-finalize: poll failed (code=${code ?? "none"}): ${msg}
|
|
8046
|
+
`
|
|
8047
|
+
);
|
|
6937
8048
|
}
|
|
6938
8049
|
void reportError("auto_finalize_stage1_fail", err);
|
|
6939
8050
|
}
|
|
@@ -6957,7 +8068,8 @@ async function useClient() {
|
|
|
6957
8068
|
cfg = { ...cfg, username, sessionId: pollResult.session_id };
|
|
6958
8069
|
await saveCliConfig(cfg);
|
|
6959
8070
|
await clearPending();
|
|
6960
|
-
if (debug)
|
|
8071
|
+
if (debug)
|
|
8072
|
+
process.stderr.write(`[kordis] auto-finalize: persisted tokens for ${username}
|
|
6961
8073
|
`);
|
|
6962
8074
|
} catch (err) {
|
|
6963
8075
|
await clearPending();
|
|
@@ -6986,31 +8098,51 @@ async function useClient() {
|
|
|
6986
8098
|
}
|
|
6987
8099
|
} : {}
|
|
6988
8100
|
});
|
|
8101
|
+
const renewTokens = async (user, sent) => {
|
|
8102
|
+
try {
|
|
8103
|
+
const refreshed = await api.refreshSession(sent);
|
|
8104
|
+
currentTokens = {
|
|
8105
|
+
access_token: refreshed.access_token,
|
|
8106
|
+
refresh_token: refreshed.refresh_token,
|
|
8107
|
+
session_id: refreshed.session_id,
|
|
8108
|
+
expires_at_epoch: Math.floor(Date.now() / 1e3) + refreshed.expires_in
|
|
8109
|
+
};
|
|
8110
|
+
await keychain.save(user, currentTokens);
|
|
8111
|
+
await saveCliConfig({ ...cfg, sessionId: refreshed.session_id });
|
|
8112
|
+
return true;
|
|
8113
|
+
} catch (refreshErr) {
|
|
8114
|
+
if (!lostRefreshRace(refreshErr)) return false;
|
|
8115
|
+
const stored = await storedPairOtherThan(user, sent);
|
|
8116
|
+
if (stored === null) return false;
|
|
8117
|
+
currentTokens = stored;
|
|
8118
|
+
return true;
|
|
8119
|
+
}
|
|
8120
|
+
};
|
|
6989
8121
|
const authedRequest = async (fn) => {
|
|
6990
8122
|
try {
|
|
6991
8123
|
return await fn(api);
|
|
6992
8124
|
} catch (err) {
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
const refreshed = await api.refreshSession(currentTokens.refresh_token);
|
|
6996
|
-
currentTokens = {
|
|
6997
|
-
access_token: refreshed.access_token,
|
|
6998
|
-
refresh_token: refreshed.refresh_token,
|
|
6999
|
-
session_id: refreshed.session_id,
|
|
7000
|
-
expires_at_epoch: Math.floor(Date.now() / 1e3) + refreshed.expires_in
|
|
7001
|
-
};
|
|
7002
|
-
await keychain.save(username, currentTokens);
|
|
7003
|
-
await saveCliConfig({ ...cfg, sessionId: refreshed.session_id });
|
|
7004
|
-
return await fn(api);
|
|
7005
|
-
} catch {
|
|
7006
|
-
throw err;
|
|
7007
|
-
}
|
|
7008
|
-
}
|
|
7009
|
-
throw err;
|
|
8125
|
+
const renewed = err instanceof KordisApiError && err.code === "not_authenticated" && currentTokens !== null && username !== null && await renewTokens(username, currentTokens.refresh_token);
|
|
8126
|
+
if (!renewed) throw err;
|
|
7010
8127
|
}
|
|
8128
|
+
return fn(api);
|
|
7011
8129
|
};
|
|
7012
8130
|
return { api, keychain, username, apiUrl: cfg.apiUrl, authedRequest };
|
|
7013
8131
|
}
|
|
8132
|
+
function lostRefreshRace(err) {
|
|
8133
|
+
return err instanceof KordisApiError && err.status === 401 && isRefreshRace({ error: { code: err.code, flags: err.flags } });
|
|
8134
|
+
}
|
|
8135
|
+
var RACE_REREADS_AT_MS = [500, 1e3, 2e3];
|
|
8136
|
+
async function storedPairOtherThan(username, sent) {
|
|
8137
|
+
let waited = 0;
|
|
8138
|
+
for (const at of RACE_REREADS_AT_MS) {
|
|
8139
|
+
await new Promise((resolve6) => setTimeout(resolve6, at - waited));
|
|
8140
|
+
waited = at;
|
|
8141
|
+
const stored = await createKeychain().then((store) => store.load(username)).catch(() => null);
|
|
8142
|
+
if (stored !== null && stored.refresh_token !== sent) return stored;
|
|
8143
|
+
}
|
|
8144
|
+
return null;
|
|
8145
|
+
}
|
|
7014
8146
|
|
|
7015
8147
|
// src/ui.ts
|
|
7016
8148
|
import kleur from "kleur";
|
|
@@ -8332,6 +9464,7 @@ function lintForLeaks(sources) {
|
|
|
8332
9464
|
}
|
|
8333
9465
|
|
|
8334
9466
|
// src/commands/push.ts
|
|
9467
|
+
init_src();
|
|
8335
9468
|
var collect = (val, acc) => acc.concat(val);
|
|
8336
9469
|
function toLineage(raws, kind) {
|
|
8337
9470
|
return (raws ?? []).map((raw) => {
|
|
@@ -8346,7 +9479,10 @@ function toLineage(raws, kind) {
|
|
|
8346
9479
|
var ASSET_MAX_BYTES = 2 * 1024 * 1024;
|
|
8347
9480
|
var BUNDLE_MAX_BYTES = 5 * 1024 * 1024;
|
|
8348
9481
|
function pushCommand() {
|
|
8349
|
-
return new Command4("push").description("publish a new package or a new version").argument("<path>", "SKILL.md file, .skill ZIP, or skill directory").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option("--slug <slug>", 'override slug; accepts "user/slug" to point at a specific package').option(
|
|
9482
|
+
return new Command4("push").description("publish a new package or a new version").argument("<path>", "SKILL.md file, .skill ZIP, or skill directory").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option("--slug <slug>", 'override slug; accepts "user/slug" to point at a specific package').option(
|
|
9483
|
+
"--type <type>",
|
|
9484
|
+
"type to write into a frontmatter that names none (else a SKILL.md, folder or .skill is a skill); refused if the frontmatter names another"
|
|
9485
|
+
).option("--changelog <text>", "optional changelog entry").option("--dry-run", "validate only; do not publish").option("--no-wait", "return immediately without waiting for review").option(
|
|
8350
9486
|
"--adapted-from <slug>",
|
|
8351
9487
|
"source this package was adapted from (repeatable); records lineage",
|
|
8352
9488
|
collect,
|
|
@@ -8361,17 +9497,15 @@ function pushCommand() {
|
|
|
8361
9497
|
let contentLabel = "SKILL.md";
|
|
8362
9498
|
if (s.isDirectory()) {
|
|
8363
9499
|
({ content, assets } = await bundleDirectory(abs));
|
|
8364
|
-
content =
|
|
9500
|
+
content = resolveType(content, opts.type, "skill");
|
|
8365
9501
|
} else if (abs.endsWith(".skill")) {
|
|
8366
9502
|
({ content, assets } = await bundleSkillZip(abs));
|
|
8367
|
-
content =
|
|
9503
|
+
content = resolveType(content, opts.type, "skill");
|
|
8368
9504
|
} else {
|
|
8369
9505
|
content = await readFile6(abs, "utf8");
|
|
8370
9506
|
contentLabel = basename(abs);
|
|
8371
9507
|
const isSkillFilename = abs.endsWith("/SKILL.md") || abs.endsWith(".skill.md");
|
|
8372
|
-
|
|
8373
|
-
content = ensureTypeField(content, "skill");
|
|
8374
|
-
}
|
|
9508
|
+
content = resolveType(content, opts.type, isSkillFilename ? "skill" : void 0);
|
|
8375
9509
|
}
|
|
8376
9510
|
let nameFromFm = null;
|
|
8377
9511
|
try {
|
|
@@ -8451,7 +9585,6 @@ function pushCommand() {
|
|
|
8451
9585
|
bump: opts.bump,
|
|
8452
9586
|
slug: opts.slug && !opts.slug.includes("/") ? opts.slug : void 0,
|
|
8453
9587
|
target_full_slug: targetFullSlug,
|
|
8454
|
-
type: opts.type,
|
|
8455
9588
|
changelog: opts.changelog,
|
|
8456
9589
|
lineage: lineage.length > 0 ? lineage : void 0,
|
|
8457
9590
|
dry_run: !!opts.dryRun
|
|
@@ -8587,15 +9720,39 @@ function printLeakWarnings(findings) {
|
|
|
8587
9720
|
printHuman(c.yellow(` - ${f.message}`));
|
|
8588
9721
|
}
|
|
8589
9722
|
}
|
|
8590
|
-
function
|
|
9723
|
+
function resolveType(content, typeFlag, inferred) {
|
|
9724
|
+
if (typeFlag === void 0) return inferred ? ensureTypeField(content, inferred) : content;
|
|
9725
|
+
const declared = frontmatterType(content);
|
|
9726
|
+
if (declared === void 0) return ensureTypeField(content, typeFlag);
|
|
9727
|
+
if (declared !== typeFlag) {
|
|
9728
|
+
throw new KordisApiError(400, {
|
|
9729
|
+
error: {
|
|
9730
|
+
code: "usage_error",
|
|
9731
|
+
message: `--type "${typeFlag}" disagrees with the type in the frontmatter, "${declared}"`,
|
|
9732
|
+
hint: `This file's frontmatter says \`type: ${declared}\`; change it or drop \`--type\`.`
|
|
9733
|
+
}
|
|
9734
|
+
});
|
|
9735
|
+
}
|
|
9736
|
+
return content;
|
|
9737
|
+
}
|
|
9738
|
+
function frontmatterType(content) {
|
|
9739
|
+
try {
|
|
9740
|
+
const value = parseFrontmatter(content).frontmatter?.["type"];
|
|
9741
|
+
return typeof value === "string" ? value : void 0;
|
|
9742
|
+
} catch {
|
|
9743
|
+
return void 0;
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9746
|
+
function ensureTypeField(content, type) {
|
|
8591
9747
|
const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
8592
9748
|
if (!fmMatch) return content;
|
|
8593
9749
|
const fmBlock = fmMatch[1];
|
|
8594
9750
|
if (/^type\s*:/m.test(fmBlock)) return content;
|
|
8595
|
-
const
|
|
8596
|
-
|
|
9751
|
+
const scalar = HarnessTypeSchema.safeParse(type).success ? type : JSON.stringify(type);
|
|
9752
|
+
const injected = fmMatch[0].replace(/^---\r?\n/, () => `---
|
|
9753
|
+
type: ${scalar}
|
|
8597
9754
|
`);
|
|
8598
|
-
return content.replace(fmMatch[0], injected);
|
|
9755
|
+
return content.replace(fmMatch[0], () => injected);
|
|
8599
9756
|
}
|
|
8600
9757
|
var EXCLUDE_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", ".gitkeep"]);
|
|
8601
9758
|
var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["__pycache__", "node_modules", ".git", "evals", "dist", ".turbo"]);
|
|
@@ -8609,15 +9766,15 @@ function shouldExclude(relPath, isDir) {
|
|
|
8609
9766
|
if (EXCLUDE_EXTS.has(ext)) return true;
|
|
8610
9767
|
return false;
|
|
8611
9768
|
}
|
|
8612
|
-
async function walkDir(
|
|
8613
|
-
const entries = await readdir2(join8(
|
|
9769
|
+
async function walkDir(base2, relDir, collected) {
|
|
9770
|
+
const entries = await readdir2(join8(base2, relDir), { withFileTypes: true });
|
|
8614
9771
|
for (const entry of entries) {
|
|
8615
9772
|
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
8616
9773
|
if (shouldExclude(rel, entry.isDirectory())) continue;
|
|
8617
9774
|
if (entry.isDirectory()) {
|
|
8618
|
-
await walkDir(
|
|
9775
|
+
await walkDir(base2, rel, collected);
|
|
8619
9776
|
} else if (entry.isFile()) {
|
|
8620
|
-
collected.push({ rel, abs: join8(
|
|
9777
|
+
collected.push({ rel, abs: join8(base2, rel) });
|
|
8621
9778
|
}
|
|
8622
9779
|
}
|
|
8623
9780
|
}
|
|
@@ -9271,45 +10428,31 @@ function getTranslator(type, format) {
|
|
|
9271
10428
|
return fn;
|
|
9272
10429
|
}
|
|
9273
10430
|
|
|
9274
|
-
// src/
|
|
9275
|
-
init_brand();
|
|
9276
|
-
import { join as join9 } from "path";
|
|
9277
|
-
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
9278
|
-
function kordisHome2() {
|
|
9279
|
-
return brandHome();
|
|
9280
|
-
}
|
|
9281
|
-
function lockfilePath() {
|
|
9282
|
-
return join9(kordisHome2(), "installed.json");
|
|
9283
|
-
}
|
|
9284
|
-
function lockfileDisplayPath() {
|
|
9285
|
-
const path2 = lockfilePath();
|
|
9286
|
-
const home = process.env["HOME"];
|
|
9287
|
-
return home && path2.startsWith(home) ? "~" + path2.slice(home.length) : path2;
|
|
9288
|
-
}
|
|
9289
|
-
async function readLockfile() {
|
|
9290
|
-
try {
|
|
9291
|
-
const raw = await readFile7(lockfilePath(), "utf8");
|
|
9292
|
-
return JSON.parse(raw);
|
|
9293
|
-
} catch {
|
|
9294
|
-
return { packages: {} };
|
|
9295
|
-
}
|
|
9296
|
-
}
|
|
9297
|
-
async function writeLockfile(lf) {
|
|
9298
|
-
await mkdir5(kordisHome2(), { recursive: true });
|
|
9299
|
-
await writeFile5(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
9300
|
-
encoding: "utf8",
|
|
9301
|
-
mode: 384
|
|
9302
|
-
});
|
|
9303
|
-
}
|
|
9304
|
-
async function recordInstalled(fullSlug, entry) {
|
|
9305
|
-
const lf = await readLockfile();
|
|
9306
|
-
lf.packages[fullSlug] = entry;
|
|
9307
|
-
await writeLockfile(lf);
|
|
9308
|
-
}
|
|
9309
|
-
|
|
9310
|
-
// src/lib/merge-emitted-file.ts
|
|
10431
|
+
// ../../packages/translators/src/merge-emitted-file.ts
|
|
9311
10432
|
import tomlLib2 from "@iarna/toml";
|
|
9312
10433
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
10434
|
+
var MergeRefusal = class extends Error {
|
|
10435
|
+
code;
|
|
10436
|
+
reason;
|
|
10437
|
+
constructor(reason) {
|
|
10438
|
+
super(refusalMessage(reason));
|
|
10439
|
+
this.name = "MergeRefusal";
|
|
10440
|
+
this.code = reason.kind === "prototype_key" ? "unsafe_package_config" : "merge_refused";
|
|
10441
|
+
this.reason = reason;
|
|
10442
|
+
}
|
|
10443
|
+
};
|
|
10444
|
+
function refusalMessage(reason) {
|
|
10445
|
+
switch (reason.kind) {
|
|
10446
|
+
case "prototype_key":
|
|
10447
|
+
return `refusing to merge config fragment \u2014 prototype-polluting key "${reason.key}" at ${reason.path}`;
|
|
10448
|
+
case "malformed_file":
|
|
10449
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing file is not valid ${reason.format.toUpperCase()}: ${reason.parseError}`;
|
|
10450
|
+
case "non_object_root":
|
|
10451
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing ${reason.format.toUpperCase()} root is not an object`;
|
|
10452
|
+
case "dangling_marker":
|
|
10453
|
+
return `refusing to merge \u2014 found a "kordis:${reason.fullSlug}" start marker with no matching end marker`;
|
|
10454
|
+
}
|
|
10455
|
+
}
|
|
9313
10456
|
function isPlainObject(v) {
|
|
9314
10457
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
9315
10458
|
}
|
|
@@ -9334,11 +10477,7 @@ function assertNoProtoKeys(value, path2) {
|
|
|
9334
10477
|
if (!isPlainObject(value)) return;
|
|
9335
10478
|
for (const [k, v] of Object.entries(value)) {
|
|
9336
10479
|
if (PROTO_KEYS.has(k)) {
|
|
9337
|
-
|
|
9338
|
-
`refusing to merge config fragment \u2014 prototype-polluting key "${k}" at ${[...path2, k].join(".")}`
|
|
9339
|
-
);
|
|
9340
|
-
e.hint = "The package emits a config key named __proto__/constructor/prototype, which no legitimate tool config needs. Report it to the author if you did not publish it.";
|
|
9341
|
-
throw e;
|
|
10480
|
+
throw new MergeRefusal({ kind: "prototype_key", key: k, path: [...path2, k].join(".") });
|
|
9342
10481
|
}
|
|
9343
10482
|
assertNoProtoKeys(v, [...path2, k]);
|
|
9344
10483
|
}
|
|
@@ -9347,19 +10486,15 @@ function isOwnedServerLeaf(path2) {
|
|
|
9347
10486
|
return path2.length === 2 && (path2[0] === "mcpServers" || path2[0] === "mcp_servers");
|
|
9348
10487
|
}
|
|
9349
10488
|
function deepMergeFragment(existing, fragment, path2 = []) {
|
|
9350
|
-
|
|
10489
|
+
assertNoProtoKeys(fragment, path2);
|
|
10490
|
+
return mergeAt(existing, fragment, path2);
|
|
10491
|
+
}
|
|
10492
|
+
function mergeAt(existing, fragment, path2) {
|
|
9351
10493
|
if (isOwnedServerLeaf(path2)) return fragment;
|
|
9352
10494
|
if (isPlainObject(existing) && isPlainObject(fragment)) {
|
|
9353
10495
|
const out = { ...existing };
|
|
9354
10496
|
for (const [k, v] of Object.entries(fragment)) {
|
|
9355
|
-
|
|
9356
|
-
const e = new Error(
|
|
9357
|
-
`refusing to merge config fragment \u2014 prototype-polluting key "${k}" at ${[...path2, k].join(".")}`
|
|
9358
|
-
);
|
|
9359
|
-
e.hint = "The package emits a config key named __proto__/constructor/prototype, which no legitimate tool config needs. Report it to the author if you did not publish it.";
|
|
9360
|
-
throw e;
|
|
9361
|
-
}
|
|
9362
|
-
out[k] = deepMergeFragment(existing[k], v, [...path2, k]);
|
|
10497
|
+
out[k] = mergeAt(existing[k], v, [...path2, k]);
|
|
9363
10498
|
}
|
|
9364
10499
|
return out;
|
|
9365
10500
|
}
|
|
@@ -9402,18 +10537,15 @@ function mergeStructuredFile(existingRaw, fragmentRaw, format, targetPath) {
|
|
|
9402
10537
|
try {
|
|
9403
10538
|
existing = parseStructured(existingRaw, format);
|
|
9404
10539
|
} catch (err) {
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
10540
|
+
throw new MergeRefusal({
|
|
10541
|
+
kind: "malformed_file",
|
|
10542
|
+
targetPath,
|
|
10543
|
+
format,
|
|
10544
|
+
parseError: err.message
|
|
10545
|
+
});
|
|
9410
10546
|
}
|
|
9411
10547
|
if (!isPlainObject(existing) && isPlainObject(fragment)) {
|
|
9412
|
-
|
|
9413
|
-
`refusing to merge into "${targetPath}" \u2014 existing ${format.toUpperCase()} root is not an object`
|
|
9414
|
-
);
|
|
9415
|
-
e.hint = `${targetPath} parses but its top level is not an object, so a config fragment can't be merged in. Move the file aside (or fix its shape), then re-run \`kordis pull\`/\`kordis update\`.`;
|
|
9416
|
-
throw e;
|
|
10548
|
+
throw new MergeRefusal({ kind: "non_object_root", targetPath, format });
|
|
9417
10549
|
}
|
|
9418
10550
|
const merged = deepMergeFragment(existing, fragment);
|
|
9419
10551
|
return stringifyStructured(merged, format);
|
|
@@ -9435,15 +10567,67 @@ function mergeFencedFile(existingRaw, fullSlug, block) {
|
|
|
9435
10567
|
return existingRaw.slice(0, startMatch.index) + trimmedBlock + existingRaw.slice(spanEnd);
|
|
9436
10568
|
}
|
|
9437
10569
|
if (startMatch && endIdx === -1) {
|
|
9438
|
-
|
|
9439
|
-
`refusing to merge \u2014 found a "kordis:${fullSlug}" start marker with no matching end marker`
|
|
9440
|
-
);
|
|
9441
|
-
e.hint = `The file has a dangling <!-- kordis:${fullSlug}:...:start --> marker. Delete that line (or restore its <!-- kordis:${fullSlug}:end --> line), then re-run \`kordis pull\`/\`kordis update\`.`;
|
|
9442
|
-
throw e;
|
|
10570
|
+
throw new MergeRefusal({ kind: "dangling_marker", fullSlug });
|
|
9443
10571
|
}
|
|
9444
10572
|
return existingRaw.trimEnd() + "\n\n" + trimmedBlock + "\n";
|
|
9445
10573
|
}
|
|
9446
10574
|
|
|
10575
|
+
// src/lockfile.ts
|
|
10576
|
+
init_brand();
|
|
10577
|
+
import { join as join9 } from "path";
|
|
10578
|
+
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
10579
|
+
function kordisHome2() {
|
|
10580
|
+
return brandHome();
|
|
10581
|
+
}
|
|
10582
|
+
function lockfilePath() {
|
|
10583
|
+
return join9(kordisHome2(), "installed.json");
|
|
10584
|
+
}
|
|
10585
|
+
function lockfileDisplayPath() {
|
|
10586
|
+
const path2 = lockfilePath();
|
|
10587
|
+
const home = process.env["HOME"];
|
|
10588
|
+
return home && path2.startsWith(home) ? "~" + path2.slice(home.length) : path2;
|
|
10589
|
+
}
|
|
10590
|
+
async function readLockfile() {
|
|
10591
|
+
try {
|
|
10592
|
+
const raw = await readFile7(lockfilePath(), "utf8");
|
|
10593
|
+
return JSON.parse(raw);
|
|
10594
|
+
} catch {
|
|
10595
|
+
return { packages: {} };
|
|
10596
|
+
}
|
|
10597
|
+
}
|
|
10598
|
+
async function writeLockfile(lf) {
|
|
10599
|
+
await mkdir5(kordisHome2(), { recursive: true });
|
|
10600
|
+
await writeFile5(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
10601
|
+
encoding: "utf8",
|
|
10602
|
+
mode: 384
|
|
10603
|
+
});
|
|
10604
|
+
}
|
|
10605
|
+
async function recordInstalled(fullSlug, entry) {
|
|
10606
|
+
const lf = await readLockfile();
|
|
10607
|
+
lf.packages[fullSlug] = entry;
|
|
10608
|
+
await writeLockfile(lf);
|
|
10609
|
+
}
|
|
10610
|
+
|
|
10611
|
+
// src/lib/merge-refusal.ts
|
|
10612
|
+
function adaptMergeRefusal(err) {
|
|
10613
|
+
if (!(err instanceof MergeRefusal)) return null;
|
|
10614
|
+
const e = new Error(err.message);
|
|
10615
|
+
e.hint = mergeRefusalHint(err.reason);
|
|
10616
|
+
return e;
|
|
10617
|
+
}
|
|
10618
|
+
function mergeRefusalHint(reason) {
|
|
10619
|
+
switch (reason.kind) {
|
|
10620
|
+
case "prototype_key":
|
|
10621
|
+
return "The package emits a config key named __proto__/constructor/prototype, which no legitimate tool config needs. Report it to the author if you did not publish it.";
|
|
10622
|
+
case "malformed_file":
|
|
10623
|
+
return `Fix or remove the malformed ${reason.targetPath}, then re-run \`kordis pull\`/\`kordis update\`. Refusing to overwrite it blind \u2014 it may hold config from other tools/packages.`;
|
|
10624
|
+
case "non_object_root":
|
|
10625
|
+
return `${reason.targetPath} parses but its top level is not an object, so a config fragment can't be merged in. Move the file aside (or fix its shape), then re-run \`kordis pull\`/\`kordis update\`.`;
|
|
10626
|
+
case "dangling_marker":
|
|
10627
|
+
return `The file has a dangling <!-- kordis:${reason.fullSlug}:...:start --> marker. Delete that line (or restore its <!-- kordis:${reason.fullSlug}:end --> line), then re-run \`kordis pull\`/\`kordis update\`.`;
|
|
10628
|
+
}
|
|
10629
|
+
}
|
|
10630
|
+
|
|
9447
10631
|
// src/commands/pull.ts
|
|
9448
10632
|
async function requireExecutableApproval(pkg, fullSlug, files, approve2) {
|
|
9449
10633
|
if (!pkg.capabilities.executable) return;
|
|
@@ -9841,7 +11025,12 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
9841
11025
|
if (err.code !== "ENOENT") throw err;
|
|
9842
11026
|
}
|
|
9843
11027
|
if (f.format === "json" || f.format === "yaml" || f.format === "toml") {
|
|
9844
|
-
|
|
11028
|
+
let merged2;
|
|
11029
|
+
try {
|
|
11030
|
+
merged2 = mergeStructuredFile(existingRaw, f.content, f.format, abs);
|
|
11031
|
+
} catch (err) {
|
|
11032
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
11033
|
+
}
|
|
9845
11034
|
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
9846
11035
|
notes?.push(
|
|
9847
11036
|
`merged into ${f.path} \u2014 comments/formatting in the existing file are not preserved`
|
|
@@ -9850,11 +11039,17 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
9850
11039
|
await writeFile6(abs, merged2, "utf8");
|
|
9851
11040
|
return;
|
|
9852
11041
|
}
|
|
9853
|
-
|
|
11042
|
+
let merged;
|
|
11043
|
+
try {
|
|
11044
|
+
merged = mergeFencedFile(existingRaw, fullSlug, f.content);
|
|
11045
|
+
} catch (err) {
|
|
11046
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
11047
|
+
}
|
|
9854
11048
|
await writeFile6(abs, merged, "utf8");
|
|
9855
11049
|
}
|
|
9856
11050
|
|
|
9857
11051
|
// src/commands/read.ts
|
|
11052
|
+
init_src2();
|
|
9858
11053
|
init_sections();
|
|
9859
11054
|
import { Command as Command6 } from "commander";
|
|
9860
11055
|
|
|
@@ -9874,6 +11069,15 @@ function untrustedFenceNotice(sentinel) {
|
|
|
9874
11069
|
}
|
|
9875
11070
|
|
|
9876
11071
|
// src/commands/read.ts
|
|
11072
|
+
var shellWord = (word) => /^[\w./@:+-]+$/.test(word) ? word : JSON.stringify(word);
|
|
11073
|
+
function readAgainCommand(fullSlug, parsed, opts) {
|
|
11074
|
+
const suffix = parsed.version ? `@${parsed.version}` : parsed.range ? `@${parsed.range}` : "";
|
|
11075
|
+
return [
|
|
11076
|
+
`kordis read ${shellWord(`${fullSlug}${suffix}`)}`,
|
|
11077
|
+
...opts.pin === void 0 ? [] : [`--pin ${shellWord(opts.pin)}`],
|
|
11078
|
+
...opts.allowPending ? ["--allow-pending"] : []
|
|
11079
|
+
].join(" ");
|
|
11080
|
+
}
|
|
9877
11081
|
function readCommand() {
|
|
9878
11082
|
return new Command6("read").description("fetch a package body into the agent context (no install)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--pin <semver>", "pin to an exact version (overrides @suffix)").option("--outline", "print only the section map (skeleton) \u2014 cheap on context", false).option("--section <name>", "print only one section (see names with --outline)").action(
|
|
9879
11083
|
async (rawSlug, opts) => {
|
|
@@ -9951,9 +11155,19 @@ Bundled files \u2014 fetch with ${c.cyan(`kordis pull ${res.full_slug}`)}:
|
|
|
9951
11155
|
const sec = sectionOf(res.body, opts.section);
|
|
9952
11156
|
if (!sec) {
|
|
9953
11157
|
const names = outlineOf(res.body).sections.map((s) => s.name).join(", ");
|
|
11158
|
+
const readAgain = readAgainCommand(res.full_slug, parsed, opts);
|
|
11159
|
+
if (isJsonMode()) {
|
|
11160
|
+
throw new KordisApiError(400, {
|
|
11161
|
+
error: {
|
|
11162
|
+
code: "usage_error",
|
|
11163
|
+
message: `section "${opts.section}" not found`,
|
|
11164
|
+
hint: names ? `Available sections: ${names}. Read one with \`${readAgain} --section <name>\`.` : `This package has no sub-sections \u2014 read the whole body with \`${readAgain}\`.`
|
|
11165
|
+
}
|
|
11166
|
+
});
|
|
11167
|
+
}
|
|
9954
11168
|
process.stderr.write(
|
|
9955
11169
|
`${c.red("\u2717")} section "${opts.section}" not found. Available: ${names || "(none \u2014 this package has no sub-sections)"}
|
|
9956
|
-
See the map with ${c.cyan(
|
|
11170
|
+
See the map with ${c.cyan(`${readAgain} --outline`)}.
|
|
9957
11171
|
`
|
|
9958
11172
|
);
|
|
9959
11173
|
process.exitCode = 2;
|
|
@@ -10023,6 +11237,7 @@ import { homedir as homedir4 } from "os";
|
|
|
10023
11237
|
import { join as join11 } from "path";
|
|
10024
11238
|
var PER_FILE_CAP = 16 * 1024;
|
|
10025
11239
|
var TOTAL_CAP = 48 * 1024;
|
|
11240
|
+
var TRUNCATION_RESERVE = 64;
|
|
10026
11241
|
async function pathExists3(p) {
|
|
10027
11242
|
try {
|
|
10028
11243
|
await access3(p);
|
|
@@ -10033,6 +11248,7 @@ async function pathExists3(p) {
|
|
|
10033
11248
|
}
|
|
10034
11249
|
function capContent(raw, cap) {
|
|
10035
11250
|
if (Buffer.byteLength(raw, "utf8") <= cap) return { content: raw, truncated: false };
|
|
11251
|
+
const fill = cap - TRUNCATION_RESERVE;
|
|
10036
11252
|
const lines = raw.split("\n");
|
|
10037
11253
|
let acc = 0;
|
|
10038
11254
|
let lastHeadingIdx = -1;
|
|
@@ -10041,7 +11257,7 @@ function capContent(raw, cap) {
|
|
|
10041
11257
|
const line = lines[i] ?? "";
|
|
10042
11258
|
const lineBytes = Buffer.byteLength(`${line}
|
|
10043
11259
|
`, "utf8");
|
|
10044
|
-
if (acc + lineBytes >
|
|
11260
|
+
if (acc + lineBytes > fill) {
|
|
10045
11261
|
cutIdx = i;
|
|
10046
11262
|
break;
|
|
10047
11263
|
}
|
|
@@ -10049,6 +11265,7 @@ function capContent(raw, cap) {
|
|
|
10049
11265
|
if (/^#{1,6}\s/.test(line)) lastHeadingIdx = i;
|
|
10050
11266
|
}
|
|
10051
11267
|
const cut = lastHeadingIdx > cutIdx / 2 ? lastHeadingIdx : cutIdx;
|
|
11268
|
+
if (cut === 0) return null;
|
|
10052
11269
|
const kept = lines.slice(0, cut).join("\n");
|
|
10053
11270
|
return {
|
|
10054
11271
|
content: `${kept}
|
|
@@ -10077,7 +11294,6 @@ async function readHarnessContext(cwd = process.cwd(), home = homedir4()) {
|
|
|
10077
11294
|
const files = [];
|
|
10078
11295
|
let totalBytes = 0;
|
|
10079
11296
|
for (const cand of candidates) {
|
|
10080
|
-
if (totalBytes >= TOTAL_CAP) break;
|
|
10081
11297
|
if (!await pathExists3(cand.path)) continue;
|
|
10082
11298
|
let raw;
|
|
10083
11299
|
try {
|
|
@@ -10086,7 +11302,9 @@ async function readHarnessContext(cwd = process.cwd(), home = homedir4()) {
|
|
|
10086
11302
|
continue;
|
|
10087
11303
|
}
|
|
10088
11304
|
const cap = Math.min(PER_FILE_CAP, TOTAL_CAP - totalBytes);
|
|
10089
|
-
const
|
|
11305
|
+
const capped2 = capContent(raw, cap);
|
|
11306
|
+
if (capped2 === null) break;
|
|
11307
|
+
const { content, truncated } = capped2;
|
|
10090
11308
|
totalBytes += Buffer.byteLength(content, "utf8");
|
|
10091
11309
|
files.push({
|
|
10092
11310
|
path: cand.display,
|
|
@@ -12743,9 +13961,9 @@ function assertNothingLost(before, after, action) {
|
|
|
12743
13961
|
});
|
|
12744
13962
|
}
|
|
12745
13963
|
}
|
|
12746
|
-
async function commit2(root,
|
|
13964
|
+
async function commit2(root, base2, next) {
|
|
12747
13965
|
const current = await readIgnoreFileState(root);
|
|
12748
|
-
if (current.hash !==
|
|
13966
|
+
if (current.hash !== base2.hash) {
|
|
12749
13967
|
return { ok: false, reason: "revision_conflict", current };
|
|
12750
13968
|
}
|
|
12751
13969
|
if (next === current.content) return { ok: true, content: next, changed: false };
|
|
@@ -12791,15 +14009,15 @@ function planRemoveIgnoreEntry(content, relPath) {
|
|
|
12791
14009
|
assertNothingLost(next, content, "un-excluding");
|
|
12792
14010
|
return { content: next, changed: true };
|
|
12793
14011
|
}
|
|
12794
|
-
async function addIgnoreEntry(root, relPath,
|
|
12795
|
-
const edit = planAddIgnoreEntry(
|
|
12796
|
-
if (!edit.changed) return { ok: true, content:
|
|
12797
|
-
return commit2(root,
|
|
14012
|
+
async function addIgnoreEntry(root, relPath, base2) {
|
|
14013
|
+
const edit = planAddIgnoreEntry(base2.content, relPath);
|
|
14014
|
+
if (!edit.changed) return { ok: true, content: base2.content, changed: false };
|
|
14015
|
+
return commit2(root, base2, edit.content);
|
|
12798
14016
|
}
|
|
12799
|
-
async function removeIgnoreEntry(root, relPath,
|
|
12800
|
-
const edit = planRemoveIgnoreEntry(
|
|
12801
|
-
if (!edit.changed) return { ok: true, content:
|
|
12802
|
-
return commit2(root,
|
|
14017
|
+
async function removeIgnoreEntry(root, relPath, base2) {
|
|
14018
|
+
const edit = planRemoveIgnoreEntry(base2.content, relPath);
|
|
14019
|
+
if (!edit.changed) return { ok: true, content: base2.content, changed: false };
|
|
14020
|
+
return commit2(root, base2, edit.content);
|
|
12803
14021
|
}
|
|
12804
14022
|
|
|
12805
14023
|
// src/commands/memory/exclude.ts
|
|
@@ -12845,8 +14063,8 @@ async function openVaultOrExplain(path2) {
|
|
|
12845
14063
|
// src/commands/memory/link.ts
|
|
12846
14064
|
init_brand();
|
|
12847
14065
|
function sameBasename(all, entry) {
|
|
12848
|
-
const
|
|
12849
|
-
return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() ===
|
|
14066
|
+
const base2 = (entry.split("/").pop() ?? entry).normalize("NFC").toLowerCase();
|
|
14067
|
+
return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() === base2);
|
|
12850
14068
|
}
|
|
12851
14069
|
function spreadAcrossFolders(rows, max) {
|
|
12852
14070
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13110,7 +14328,7 @@ function excludeCommand() {
|
|
|
13110
14328
|
return new Command28("exclude").description("stop reading a note entirely \u2014 the note itself is not changed").argument("<path>", "vault-relative path of the note, e.g. decisions/launch-date.md").option("--yes", "skip the confirmation prompt").option("--vault <path>", "which linked vault the note is in").action(async (path2, opts) => {
|
|
13111
14329
|
const root = await resolveVaultRoot(opts.vault);
|
|
13112
14330
|
const target = await mustMatchAFile(root, path2);
|
|
13113
|
-
const
|
|
14331
|
+
const base2 = await readIgnoreFileState(root);
|
|
13114
14332
|
if (!opts.yes && isJsonMode()) {
|
|
13115
14333
|
throw memoryError(
|
|
13116
14334
|
"refusing to change what this vault reads without confirmation",
|
|
@@ -13134,7 +14352,7 @@ function excludeCommand() {
|
|
|
13134
14352
|
return;
|
|
13135
14353
|
}
|
|
13136
14354
|
}
|
|
13137
|
-
const result = await addIgnoreEntry(root, target,
|
|
14355
|
+
const result = await addIgnoreEntry(root, target, base2);
|
|
13138
14356
|
if (!result.ok) {
|
|
13139
14357
|
printDual({
|
|
13140
14358
|
json: {
|
|
@@ -13172,7 +14390,7 @@ function unexcludeCommand() {
|
|
|
13172
14390
|
return new Command28("unexclude").description("read a note again after excluding it").argument("<path>", "vault-relative path of the note").option("--yes", "skip the confirmation prompt").option("--vault <path>", "which linked vault the note is in").action(async (path2, opts) => {
|
|
13173
14391
|
const root = await resolveVaultRoot(opts.vault);
|
|
13174
14392
|
const target = path2.normalize("NFC");
|
|
13175
|
-
const
|
|
14393
|
+
const base2 = await readIgnoreFileState(root);
|
|
13176
14394
|
if (!opts.yes && isJsonMode()) {
|
|
13177
14395
|
throw memoryError(
|
|
13178
14396
|
"refusing to change what this vault reads without confirmation",
|
|
@@ -13195,7 +14413,7 @@ function unexcludeCommand() {
|
|
|
13195
14413
|
return;
|
|
13196
14414
|
}
|
|
13197
14415
|
}
|
|
13198
|
-
const result = await removeIgnoreEntry(root, target,
|
|
14416
|
+
const result = await removeIgnoreEntry(root, target, base2);
|
|
13199
14417
|
if (!result.ok) {
|
|
13200
14418
|
printDual({
|
|
13201
14419
|
json: {
|