@codyswann/lisa 2.217.8 → 2.218.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/dist/core/learnings-contract.d.ts +38 -0
- package/dist/core/learnings-contract.d.ts.map +1 -0
- package/dist/core/learnings-contract.js +36 -0
- package/dist/core/learnings-contract.js.map +1 -0
- package/dist/core/learnings-entry.d.ts +9 -0
- package/dist/core/learnings-entry.d.ts.map +1 -0
- package/dist/core/learnings-entry.js +233 -0
- package/dist/core/learnings-entry.js.map +1 -0
- package/dist/core/learnings-file-safety.d.ts +23 -0
- package/dist/core/learnings-file-safety.d.ts.map +1 -0
- package/dist/core/learnings-file-safety.js +84 -0
- package/dist/core/learnings-file-safety.js.map +1 -0
- package/dist/core/learnings-lock.d.ts +8 -0
- package/dist/core/learnings-lock.d.ts.map +1 -0
- package/dist/core/learnings-lock.js +227 -0
- package/dist/core/learnings-lock.js.map +1 -0
- package/dist/core/learnings-writer.d.ts +22 -0
- package/dist/core/learnings-writer.d.ts.map +1 -0
- package/dist/core/learnings-writer.js +128 -0
- package/dist/core/learnings-writer.js.map +1 -0
- package/dist/core/learnings.d.ts +4 -0
- package/dist/core/learnings.d.ts.map +1 -0
- package/dist/core/learnings.js +4 -0
- package/dist/core/learnings.js.map +1 -0
- package/dist/core/project-config.d.ts +19 -0
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +83 -14
- package/dist/core/project-config.js.map +1 -1
- package/dist/sync/registry.d.ts +0 -12
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +18 -0
- package/dist/sync/registry.js.map +1 -1
- package/package.json +2 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa/rules/eager/config-resolution.md +9 -0
- package/plugins/lisa/rules/reference/config-resolution.md +2 -0
- package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/eager/config-resolution.md +9 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +2 -0
- package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +2 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +9 -0
- package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/eager/config-resolution.md +9 -0
- package/plugins/src/base/rules/reference/config-resolution.md +2 -0
- package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +3 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executable contract shared by every project-learnings writer and checker.
|
|
3
|
+
* Keeping the numeric limits here prevents documentation, writers, and CI
|
|
4
|
+
* from drifting to different budgets.
|
|
5
|
+
* @module learnings-contract
|
|
6
|
+
*/
|
|
7
|
+
export declare const LEARNING_CONFIDENCE_VALUES: readonly ["low", "medium", "high"];
|
|
8
|
+
/** Confidence vocabulary persisted in every learning entry. */
|
|
9
|
+
export type LearningConfidence = (typeof LEARNING_CONFIDENCE_VALUES)[number];
|
|
10
|
+
export declare const LEARNINGS_CONTRACT: Readonly<{
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
readonly fields: readonly ["id", "rule", "why", "provenance", "first_learned", "last_confirmed", "confidence"];
|
|
13
|
+
readonly maxRuleCharacters: 240;
|
|
14
|
+
readonly maxRuleLines: 2;
|
|
15
|
+
readonly maxProvenanceReferences: 20;
|
|
16
|
+
readonly maxEntries: 20;
|
|
17
|
+
readonly maxTokens: 4000;
|
|
18
|
+
readonly measurement: "utf8-bytes-upper-bound";
|
|
19
|
+
}>;
|
|
20
|
+
/** Complete persisted schema for one project learning. */
|
|
21
|
+
export interface LearningEntry {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly rule: string;
|
|
24
|
+
readonly why: string;
|
|
25
|
+
readonly provenance: readonly string[];
|
|
26
|
+
readonly first_learned: string;
|
|
27
|
+
readonly last_confirmed: string;
|
|
28
|
+
readonly confidence: LearningConfidence;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Estimate tokens deterministically without coupling the persisted contract to
|
|
32
|
+
* a model-specific tokenizer. A byte count is a conservative upper bound for
|
|
33
|
+
* byte-level tokenizers and is reproducible in both the writer and CI.
|
|
34
|
+
* @param content - Canonical full Markdown document
|
|
35
|
+
* @returns Reproducible estimated token count
|
|
36
|
+
*/
|
|
37
|
+
export declare function estimateLearningTokens(content: string): number;
|
|
38
|
+
//# sourceMappingURL=learnings-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-contract.d.ts","sourceRoot":"","sources":["../../src/core/learnings-contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,0BAA0B,oCAAqC,CAAC;AAE7E,+DAA+D;AAC/D,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,eAAO,MAAM,kBAAkB;;;;;;;;;EAiBpB,CAAC;AAEZ,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executable contract shared by every project-learnings writer and checker.
|
|
3
|
+
* Keeping the numeric limits here prevents documentation, writers, and CI
|
|
4
|
+
* from drifting to different budgets.
|
|
5
|
+
* @module learnings-contract
|
|
6
|
+
*/
|
|
7
|
+
export const LEARNING_CONFIDENCE_VALUES = ["low", "medium", "high"];
|
|
8
|
+
export const LEARNINGS_CONTRACT = Object.freeze({
|
|
9
|
+
version: 1,
|
|
10
|
+
fields: Object.freeze([
|
|
11
|
+
"id",
|
|
12
|
+
"rule",
|
|
13
|
+
"why",
|
|
14
|
+
"provenance",
|
|
15
|
+
"first_learned",
|
|
16
|
+
"last_confirmed",
|
|
17
|
+
"confidence",
|
|
18
|
+
]),
|
|
19
|
+
maxRuleCharacters: 240,
|
|
20
|
+
maxRuleLines: 2,
|
|
21
|
+
maxProvenanceReferences: 20,
|
|
22
|
+
maxEntries: 20,
|
|
23
|
+
maxTokens: 4000,
|
|
24
|
+
measurement: "utf8-bytes-upper-bound",
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Estimate tokens deterministically without coupling the persisted contract to
|
|
28
|
+
* a model-specific tokenizer. A byte count is a conservative upper bound for
|
|
29
|
+
* byte-level tokenizers and is reproducible in both the writer and CI.
|
|
30
|
+
* @param content - Canonical full Markdown document
|
|
31
|
+
* @returns Reproducible estimated token count
|
|
32
|
+
*/
|
|
33
|
+
export function estimateLearningTokens(content) {
|
|
34
|
+
return Buffer.byteLength(content, "utf8");
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=learnings-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-contract.js","sourceRoot":"","sources":["../../src/core/learnings-contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAK7E,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;QACpB,IAAI;QACJ,MAAM;QACN,KAAK;QACL,YAAY;QACZ,eAAe;QACf,gBAAgB;QAChB,YAAY;KACJ,CAAC;IACX,iBAAiB,EAAE,GAAG;IACtB,YAAY,EAAE,CAAC;IACf,uBAAuB,EAAE,EAAE;IAC3B,UAAU,EAAE,EAAE;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,wBAAwB;CAC7B,CAAC,CAAC;AAaZ;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,OAAO,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Strict validation for the seven-field project learning schema. */
|
|
2
|
+
import { type LearningEntry } from "./learnings-contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validate an untrusted entry and return a normalized immutable copy.
|
|
5
|
+
* @param candidate - Value to validate against the executable contract
|
|
6
|
+
* @returns Validated learning entry
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateLearningEntry(candidate: unknown): LearningEntry;
|
|
9
|
+
//# sourceMappingURL=learnings-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-entry.d.ts","sourceRoot":"","sources":["../../src/core/learnings-entry.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAOjC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,CA0BvE"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/** Strict validation for the seven-field project learning schema. */
|
|
2
|
+
import { LEARNINGS_CONTRACT, LEARNING_CONFIDENCE_VALUES, } from "./learnings-contract.js";
|
|
3
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4
|
+
const STABLE_ID = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Validate an untrusted entry and return a normalized immutable copy.
|
|
7
|
+
* @param candidate - Value to validate against the executable contract
|
|
8
|
+
* @returns Validated learning entry
|
|
9
|
+
*/
|
|
10
|
+
export function validateLearningEntry(candidate) {
|
|
11
|
+
const descriptors = requireEntryDescriptors(candidate);
|
|
12
|
+
const value = (field) => readDataProperty(descriptors, field);
|
|
13
|
+
const id = requireStableId(value("id"));
|
|
14
|
+
const rule = requireRule(value("rule"));
|
|
15
|
+
const why = requireWhy(value("why"));
|
|
16
|
+
const provenance = requireProvenance(value("provenance"));
|
|
17
|
+
const firstLearned = requireIsoDate(value("first_learned"), "first_learned");
|
|
18
|
+
const lastConfirmed = requireIsoDate(value("last_confirmed"), "last_confirmed");
|
|
19
|
+
const confidence = requireConfidence(value("confidence"));
|
|
20
|
+
if (lastConfirmed < firstLearned) {
|
|
21
|
+
throw new Error("Invalid dates: last_confirmed precedes first_learned");
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
id,
|
|
25
|
+
rule,
|
|
26
|
+
why,
|
|
27
|
+
provenance: Object.freeze(provenance),
|
|
28
|
+
first_learned: firstLearned,
|
|
29
|
+
last_confirmed: lastConfirmed,
|
|
30
|
+
confidence,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Require an object with exactly seven accessor-free own fields.
|
|
35
|
+
* @param candidate - Untrusted candidate object
|
|
36
|
+
* @returns Exact own-property descriptor map
|
|
37
|
+
*/
|
|
38
|
+
function requireEntryDescriptors(candidate) {
|
|
39
|
+
if (candidate === null ||
|
|
40
|
+
typeof candidate !== "object" ||
|
|
41
|
+
Array.isArray(candidate)) {
|
|
42
|
+
throw new Error("Invalid learning entry: expected an object");
|
|
43
|
+
}
|
|
44
|
+
const descriptors = Object.getOwnPropertyDescriptors(candidate);
|
|
45
|
+
const ownKeys = Reflect.ownKeys(descriptors);
|
|
46
|
+
if (ownKeys.some(key => typeof key !== "string")) {
|
|
47
|
+
throw new Error("Invalid learning entry fields: symbol keys are not allowed");
|
|
48
|
+
}
|
|
49
|
+
if (ownKeys.length !== LEARNINGS_CONTRACT.fields.length ||
|
|
50
|
+
LEARNINGS_CONTRACT.fields.some(field => descriptors[field] === undefined)) {
|
|
51
|
+
throw new Error(`Invalid learning entry fields: expected exactly ${LEARNINGS_CONTRACT.fields.join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
return descriptors;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read one exact-schema field without invoking an accessor.
|
|
57
|
+
* @param descriptors - Candidate descriptor map
|
|
58
|
+
* @param field - Required contract field
|
|
59
|
+
* @returns Stored data value
|
|
60
|
+
*/
|
|
61
|
+
function readDataProperty(descriptors, field) {
|
|
62
|
+
const descriptor = descriptors[field];
|
|
63
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
64
|
+
throw new Error(`Invalid ${field}: accessors are not allowed`);
|
|
65
|
+
}
|
|
66
|
+
return descriptor.value;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Require and bound a stable learning id.
|
|
70
|
+
* @param value - Untrusted id value
|
|
71
|
+
* @returns Valid stable id
|
|
72
|
+
*/
|
|
73
|
+
function requireStableId(value) {
|
|
74
|
+
const id = requireNonEmptyString(value, "id");
|
|
75
|
+
assertUtf8Budget(id, "id");
|
|
76
|
+
if (!STABLE_ID.test(id)) {
|
|
77
|
+
throw new Error("Invalid learning id: use lowercase letters, numbers, dots, underscores, or hyphens");
|
|
78
|
+
}
|
|
79
|
+
return id;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Require a rule within both hard character and line caps.
|
|
83
|
+
* @param value - Untrusted rule value
|
|
84
|
+
* @returns Valid bounded rule
|
|
85
|
+
*/
|
|
86
|
+
function requireRule(value) {
|
|
87
|
+
const rule = requireNonEmptyString(value, "rule");
|
|
88
|
+
if (rule.length > LEARNINGS_CONTRACT.maxRuleCharacters) {
|
|
89
|
+
throw new Error(`rule exceeds maxRuleCharacters ${LEARNINGS_CONTRACT.maxRuleCharacters}`);
|
|
90
|
+
}
|
|
91
|
+
const ruleLines = rule
|
|
92
|
+
.replaceAll("\r\n", "\n")
|
|
93
|
+
.replaceAll("\r", "\n")
|
|
94
|
+
.replaceAll("\u0085", "\n")
|
|
95
|
+
.replaceAll("\u2028", "\n")
|
|
96
|
+
.replaceAll("\u2029", "\n")
|
|
97
|
+
.split("\n").length;
|
|
98
|
+
if (ruleLines > LEARNINGS_CONTRACT.maxRuleLines) {
|
|
99
|
+
throw new Error(`rule exceeds maxRuleLines ${LEARNINGS_CONTRACT.maxRuleLines}`);
|
|
100
|
+
}
|
|
101
|
+
return rule;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Require bounded explanatory text.
|
|
105
|
+
* @param value - Untrusted why value
|
|
106
|
+
* @returns Valid bounded explanation
|
|
107
|
+
*/
|
|
108
|
+
function requireWhy(value) {
|
|
109
|
+
const why = requireNonBlankText(value, "why");
|
|
110
|
+
assertUtf8Budget(why, "why");
|
|
111
|
+
return why;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate a dense, accessor-free provenance list with bounded allocation.
|
|
115
|
+
* @param value - Untrusted provenance value
|
|
116
|
+
* @returns Valid provenance references
|
|
117
|
+
*/
|
|
118
|
+
function requireProvenance(value) {
|
|
119
|
+
if (!Array.isArray(value)) {
|
|
120
|
+
throw new Error("Invalid provenance: expected an array of references");
|
|
121
|
+
}
|
|
122
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
123
|
+
const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
|
|
124
|
+
if (typeof length !== "number" ||
|
|
125
|
+
!Number.isSafeInteger(length) ||
|
|
126
|
+
length < 1 ||
|
|
127
|
+
length > LEARNINGS_CONTRACT.maxProvenanceReferences) {
|
|
128
|
+
throw new Error(`Invalid provenance: expected 1-${LEARNINGS_CONTRACT.maxProvenanceReferences} references`);
|
|
129
|
+
}
|
|
130
|
+
const provenance = Array.from({ length }, (_unused, index) => requireProvenanceItem(descriptors, index));
|
|
131
|
+
if (new Set(provenance).size !== provenance.length) {
|
|
132
|
+
throw new Error("Invalid provenance: duplicate references are not allowed");
|
|
133
|
+
}
|
|
134
|
+
return provenance;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read and bound one accessor-free provenance element.
|
|
138
|
+
* @param descriptors - Provenance array descriptors
|
|
139
|
+
* @param index - Reference index
|
|
140
|
+
* @returns Valid provenance reference
|
|
141
|
+
*/
|
|
142
|
+
function requireProvenanceItem(descriptors, index) {
|
|
143
|
+
const descriptor = descriptors[String(index)];
|
|
144
|
+
if (descriptor === undefined || !("value" in descriptor)) {
|
|
145
|
+
throw new Error(`Invalid provenance[${index}]: accessors are not allowed`);
|
|
146
|
+
}
|
|
147
|
+
const reference = requireNonEmptyString(descriptor.value, `provenance[${index}]`);
|
|
148
|
+
assertUtf8Budget(reference, `provenance[${index}]`);
|
|
149
|
+
return reference;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Require one of the persisted confidence values.
|
|
153
|
+
* @param value - Untrusted confidence value
|
|
154
|
+
* @returns Valid confidence
|
|
155
|
+
*/
|
|
156
|
+
function requireConfidence(value) {
|
|
157
|
+
if (typeof value !== "string" ||
|
|
158
|
+
!LEARNING_CONFIDENCE_VALUES.includes(value)) {
|
|
159
|
+
throw new Error(`Invalid confidence: expected ${LEARNING_CONFIDENCE_VALUES.join(" | ")}`);
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Require a trimmed, non-empty, control-character-free string.
|
|
165
|
+
* @param value - Untrusted string value
|
|
166
|
+
* @param field - Field name for errors
|
|
167
|
+
* @returns Valid non-empty string
|
|
168
|
+
*/
|
|
169
|
+
function requireNonEmptyString(value, field) {
|
|
170
|
+
if (typeof value !== "string" ||
|
|
171
|
+
value.length === 0 ||
|
|
172
|
+
value.trim() !== value ||
|
|
173
|
+
containsForbiddenTextControl(value)) {
|
|
174
|
+
throw new Error(`Invalid ${field}: expected a trimmed non-empty string`);
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Require non-blank text and normalize its outer whitespace.
|
|
180
|
+
* @param value - Untrusted text value
|
|
181
|
+
* @param field - Field name for errors
|
|
182
|
+
* @returns Normalized non-blank text
|
|
183
|
+
*/
|
|
184
|
+
function requireNonBlankText(value, field) {
|
|
185
|
+
if (typeof value !== "string" ||
|
|
186
|
+
value.trim().length === 0 ||
|
|
187
|
+
containsForbiddenTextControl(value)) {
|
|
188
|
+
throw new Error(`Invalid ${field}: expected non-blank text`);
|
|
189
|
+
}
|
|
190
|
+
return value.trim();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Require a real calendar date in ISO date form.
|
|
194
|
+
* @param value - Untrusted date value
|
|
195
|
+
* @param field - Field name for errors
|
|
196
|
+
* @returns Valid ISO date
|
|
197
|
+
*/
|
|
198
|
+
function requireIsoDate(value, field) {
|
|
199
|
+
const result = requireNonEmptyString(value, field);
|
|
200
|
+
const date = new Date(`${result}T00:00:00.000Z`);
|
|
201
|
+
if (!ISO_DATE.test(result) ||
|
|
202
|
+
Number.isNaN(date.valueOf()) ||
|
|
203
|
+
date.toISOString().slice(0, 10) !== result) {
|
|
204
|
+
throw new Error(`Invalid ${field}: expected a real YYYY-MM-DD date`);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Bound individual untrusted fields before document rendering allocates them.
|
|
210
|
+
* @param value - Valid string value
|
|
211
|
+
* @param field - Field name for errors
|
|
212
|
+
*/
|
|
213
|
+
function assertUtf8Budget(value, field) {
|
|
214
|
+
if (Buffer.byteLength(value, "utf8") > LEARNINGS_CONTRACT.maxTokens) {
|
|
215
|
+
throw new Error(`${field} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Reject non-whitespace controls while allowing rule line separators.
|
|
220
|
+
* @param value - Entry text
|
|
221
|
+
* @returns True when a forbidden control is present
|
|
222
|
+
*/
|
|
223
|
+
function containsForbiddenTextControl(value) {
|
|
224
|
+
return Array.from(value).some(character => {
|
|
225
|
+
const code = character.charCodeAt(0);
|
|
226
|
+
return (code <= 8 ||
|
|
227
|
+
code === 11 ||
|
|
228
|
+
code === 12 ||
|
|
229
|
+
(code >= 14 && code <= 31) ||
|
|
230
|
+
code === 127);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=learnings-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-entry.js","sourceRoot":"","sources":["../../src/core/learnings-entry.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EACL,kBAAkB,EAClB,0BAA0B,GAG3B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,QAAQ,GAAG,qBAAqB,CAAC;AACvC,MAAM,SAAS,GAAG,gCAAgC,CAAC;AAInD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAkB;IACtD,MAAM,WAAW,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAW,EAAE,CAC3C,gBAAgB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;IAC7E,MAAM,aAAa,GAAG,cAAc,CAClC,KAAK,CAAC,gBAAgB,CAAC,EACvB,gBAAgB,CACjB,CAAC;IACF,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IAC1D,IAAI,aAAa,GAAG,YAAY,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,EAAE;QACF,IAAI;QACJ,GAAG;QACH,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;QACrC,aAAa,EAAE,YAAY;QAC3B,cAAc,EAAE,aAAa;QAC7B,UAAU;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,SAAkB;IACjD,IACE,SAAS,KAAK,IAAI;QAClB,OAAO,SAAS,KAAK,QAAQ;QAC7B,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EACxB,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,IACE,OAAO,CAAC,MAAM,KAAK,kBAAkB,CAAC,MAAM,CAAC,MAAM;QACnD,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,EACzE,CAAC;QACD,MAAM,IAAI,KAAK,CACb,mDAAmD,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,WAAkC,EAClC,KAAiB;IAEjB,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,6BAA6B,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,UAAU,CAAC,KAAgB,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAc;IACrC,MAAM,EAAE,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,MAAM,GAAG,kBAAkB,CAAC,iBAAiB,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,kCAAkC,kBAAkB,CAAC,iBAAiB,EAAE,CACzE,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,IAAI;SACnB,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;SACxB,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC;SACtB,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC;SAC1B,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC;SAC1B,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC;SAC1B,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACtB,IAAI,SAAS,GAAG,kBAAkB,CAAC,YAAY,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,6BAA6B,kBAAkB,CAAC,YAAY,EAAE,CAC/D,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAAc;IAChC,MAAM,GAAG,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC9C,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,yBAAyB,CAClD,KAAK,CAC8B,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IACvE,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC;QAC7B,MAAM,GAAG,CAAC;QACV,MAAM,GAAG,kBAAkB,CAAC,uBAAuB,EACnD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kCAAkC,kBAAkB,CAAC,uBAAuB,aAAa,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAC3D,qBAAqB,CAAC,WAAW,EAAE,KAAK,CAAC,CAC1C,CAAC;IACF,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAC5B,WAAkC,EAClC,KAAa;IAEb,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9C,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,8BAA8B,CAAC,CAAC;IAC7E,CAAC;IACD,MAAM,SAAS,GAAG,qBAAqB,CACrC,UAAU,CAAC,KAAK,EAChB,cAAc,KAAK,GAAG,CACvB,CAAC;IACF,gBAAgB,CAAC,SAAS,EAAE,cAAc,KAAK,GAAG,CAAC,CAAC;IACpD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAc;IACvC,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAE,0BAAgD,CAAC,QAAQ,CAAC,KAAK,CAAC,EAClE,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gCAAgC,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACzE,CAAC;IACJ,CAAC;IACD,OAAO,KAA2B,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,KAAc,EAAE,KAAa;IAC1D,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK;QACtB,4BAA4B,CAAC,KAAK,CAAC,EACnC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,uCAAuC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,KAAc,EAAE,KAAa;IACxD,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QACzB,4BAA4B,CAAC,KAAK,CAAC,EACnC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,2BAA2B,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAc,EAAE,KAAa;IACnD,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,gBAAgB,CAAC,CAAC;IACjD,IACE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QACtB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,EAC1C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,mCAAmC,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,KAAa,EAAE,KAAa;IACpD,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,sBAAsB,kBAAkB,CAAC,SAAS,EAAE,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,4BAA4B,CAAC,KAAa;IACjD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;QACxC,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,CACL,IAAI,IAAI,CAAC;YACT,IAAI,KAAK,EAAE;YACX,IAAI,KAAK,EAAE;YACX,CAAC,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;YAC1B,IAAI,KAAK,GAAG,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the configured target without permitting project-root escape.
|
|
3
|
+
* @param projectRoot - Host project root
|
|
4
|
+
* @param relativeFile - Configured project-relative file
|
|
5
|
+
* @returns Resolved root and target paths
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveSafeLearningTarget(projectRoot: string, relativeFile: string): {
|
|
8
|
+
readonly root: string;
|
|
9
|
+
readonly target: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Read only a bounded regular file from the inode that was safety-checked.
|
|
13
|
+
* @param target - Resolved learnings file path
|
|
14
|
+
* @returns Existing content, or undefined when the file does not exist
|
|
15
|
+
*/
|
|
16
|
+
export declare function readExistingLearnings(target: string): Promise<string | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* Reject parent-directory symlinks that resolve outside the project root.
|
|
19
|
+
* @param root - Resolved project root
|
|
20
|
+
* @param parent - Target parent directory
|
|
21
|
+
*/
|
|
22
|
+
export declare function assertSafeLearningParents(root: string, parent: string): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=learnings-file-safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-file-safety.d.ts","sourceRoot":"","sources":["../../src/core/learnings-file-safety.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASpD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmC7B;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Filesystem containment and safe reads for project learnings. */
|
|
2
|
+
import * as fse from "fs-extra";
|
|
3
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { LEARNINGS_CONTRACT } from "./learnings-contract.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the configured target without permitting project-root escape.
|
|
8
|
+
* @param projectRoot - Host project root
|
|
9
|
+
* @param relativeFile - Configured project-relative file
|
|
10
|
+
* @returns Resolved root and target paths
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSafeLearningTarget(projectRoot, relativeFile) {
|
|
13
|
+
const root = path.resolve(projectRoot);
|
|
14
|
+
const target = path.resolve(root, relativeFile);
|
|
15
|
+
if (target === root || !target.startsWith(`${root}${path.sep}`)) {
|
|
16
|
+
throw new Error("Unsafe projectRulesFile: learnings path escapes project root");
|
|
17
|
+
}
|
|
18
|
+
return { root, target };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read only a bounded regular file from the inode that was safety-checked.
|
|
22
|
+
* @param target - Resolved learnings file path
|
|
23
|
+
* @returns Existing content, or undefined when the file does not exist
|
|
24
|
+
*/
|
|
25
|
+
export async function readExistingLearnings(target) {
|
|
26
|
+
try {
|
|
27
|
+
const beforeOpen = await lstat(target);
|
|
28
|
+
if (!beforeOpen.isFile()) {
|
|
29
|
+
throw new Error("Unsafe project learnings path: target is not a regular file");
|
|
30
|
+
}
|
|
31
|
+
const handle = await open(target, "r");
|
|
32
|
+
try {
|
|
33
|
+
const opened = await handle.stat();
|
|
34
|
+
if (!opened.isFile() ||
|
|
35
|
+
opened.dev !== beforeOpen.dev ||
|
|
36
|
+
opened.ino !== beforeOpen.ino) {
|
|
37
|
+
throw new Error("Unsafe project learnings path: target changed during open");
|
|
38
|
+
}
|
|
39
|
+
if (opened.size > LEARNINGS_CONTRACT.maxTokens) {
|
|
40
|
+
throw new Error(`Project learnings payload exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
|
|
41
|
+
}
|
|
42
|
+
return handle.readFile({ encoding: "utf8" });
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await handle.close();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.code === "ENOENT") {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reject parent-directory symlinks that resolve outside the project root.
|
|
57
|
+
* @param root - Resolved project root
|
|
58
|
+
* @param parent - Target parent directory
|
|
59
|
+
*/
|
|
60
|
+
export async function assertSafeLearningParents(root, parent) {
|
|
61
|
+
const realRoot = await realpath(root);
|
|
62
|
+
const cursor = await findExistingAncestor(parent);
|
|
63
|
+
const realParent = await realpath(cursor);
|
|
64
|
+
if (realParent !== realRoot &&
|
|
65
|
+
!realParent.startsWith(`${realRoot}${path.sep}`)) {
|
|
66
|
+
throw new Error("Unsafe project learnings path: parent escapes project root");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Find the nearest existing ancestor without mutable traversal state.
|
|
71
|
+
* @param candidate - Starting filesystem path
|
|
72
|
+
* @returns Nearest existing ancestor
|
|
73
|
+
*/
|
|
74
|
+
async function findExistingAncestor(candidate) {
|
|
75
|
+
if (await fse.pathExists(candidate)) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
const parent = path.dirname(candidate);
|
|
79
|
+
if (parent === candidate) {
|
|
80
|
+
throw new Error("Unsafe project learnings path: no project ancestor");
|
|
81
|
+
}
|
|
82
|
+
return findExistingAncestor(parent);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=learnings-file-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-file-safety.js","sourceRoot":"","sources":["../../src/core/learnings-file-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize same-target writers across processes with an exclusive hard link.
|
|
3
|
+
* @param target - Absolute learnings target
|
|
4
|
+
* @param operation - Complete read, validate, and atomic-write transaction
|
|
5
|
+
* @returns Operation result
|
|
6
|
+
*/
|
|
7
|
+
export declare function withLearningTargetLock<T>(target: string, operation: () => Promise<T>): Promise<T>;
|
|
8
|
+
//# sourceMappingURL=learnings-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-lock.d.ts","sourceRoot":"","sources":["../../src/core/learnings-lock.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAaZ"}
|