@codyswann/lisa 2.236.0 → 2.237.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/cli/check-learnings-budget-cmd.d.ts +20 -0
- package/dist/cli/check-learnings-budget-cmd.d.ts.map +1 -0
- package/dist/cli/check-learnings-budget-cmd.js +59 -0
- package/dist/cli/check-learnings-budget-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +29 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/core/learnings-budget-check.d.ts +42 -0
- package/dist/core/learnings-budget-check.d.ts.map +1 -0
- package/dist/core/learnings-budget-check.js +210 -0
- package/dist/core/learnings-budget-check.js.map +1 -0
- package/dist/core/learnings-document.js +6 -1
- package/dist/core/learnings-document.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- 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-cursor/.claude-plugin/plugin.json +1 -1
- 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/scripts/check-learnings-budget.ts +29 -163
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.237.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.237.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.237.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.237.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.237.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
/** CI gate for the canonical project learnings document and its hard budgets. */
|
|
2
|
-
import {
|
|
3
|
-
import { open } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import type * as
|
|
5
|
+
import type * as BudgetCheckModule from "../src/core/learnings-budget-check.js";
|
|
7
6
|
|
|
8
|
-
type
|
|
9
|
-
typeof
|
|
10
|
-
| "
|
|
11
|
-
| "estimateLearningTokens"
|
|
12
|
-
| "parseLearningsFile"
|
|
13
|
-
| "renderLearningsFile"
|
|
14
|
-
| "validateLearningEntry"
|
|
7
|
+
type BudgetChecker = Pick<
|
|
8
|
+
typeof BudgetCheckModule,
|
|
9
|
+
"checkLearningsBudget" | "formatDiagnosticPath"
|
|
15
10
|
>;
|
|
16
11
|
|
|
17
12
|
const DEFAULT_LEARNINGS_FILE = path.resolve(
|
|
@@ -36,178 +31,49 @@ async function main(): Promise<void> {
|
|
|
36
31
|
? DEFAULT_LEARNINGS_FILE
|
|
37
32
|
: path.resolve(process.cwd(), arguments_[0] as string);
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parseLearningsFile,
|
|
44
|
-
renderLearningsFile,
|
|
45
|
-
validateLearningEntry,
|
|
46
|
-
} = await loadLearningsModule();
|
|
47
|
-
const content = await readBoundedRegularFile(
|
|
48
|
-
file,
|
|
49
|
-
LEARNINGS_CONTRACT.maxTokens
|
|
50
|
-
);
|
|
51
|
-
const measuredTokens = estimateLearningTokens(content);
|
|
52
|
-
if (measuredTokens > LEARNINGS_CONTRACT.maxTokens) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`maxTokens exceeded: measured ${measuredTokens}, allowed ${LEARNINGS_CONTRACT.maxTokens}`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const entries = parseLearningsFile(content);
|
|
59
|
-
for (const entry of entries) {
|
|
60
|
-
validateLearningEntry(entry);
|
|
61
|
-
}
|
|
62
|
-
if (renderLearningsFile(entries) !== content) {
|
|
63
|
-
throw new Error("non-canonical project learnings format");
|
|
64
|
-
}
|
|
65
|
-
|
|
34
|
+
const { checkLearningsBudget, formatDiagnosticPath } =
|
|
35
|
+
await loadBudgetChecker();
|
|
36
|
+
const result = await checkLearningsBudget(file);
|
|
37
|
+
if (result.kind === "ok") {
|
|
66
38
|
console.log(
|
|
67
|
-
`${formatDiagnosticPath(file)}: learnings budget passed (${
|
|
68
|
-
);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
fail(`${formatDiagnosticPath(file)}: ${formatErrorDetail(error)}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Render a caught failure without allowing filesystem paths or control bytes
|
|
76
|
-
* embedded in an Error message to forge additional terminal/CI output.
|
|
77
|
-
* @param error - Unknown thrown failure
|
|
78
|
-
* @returns Stable, single-line diagnostic detail
|
|
79
|
-
*/
|
|
80
|
-
function formatErrorDetail(error: unknown): string {
|
|
81
|
-
if (error !== null && typeof error === "object") {
|
|
82
|
-
const code = readOwnString(error, "code");
|
|
83
|
-
if (code !== undefined && /^[A-Z][A-Z0-9_]*$/u.test(code)) {
|
|
84
|
-
const syscall = readOwnString(error, "syscall");
|
|
85
|
-
return syscall === undefined
|
|
86
|
-
? `filesystem error ${code}`
|
|
87
|
-
: `filesystem error ${code} during ${escapeDiagnosticText(syscall)}`;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
-
return escapeDiagnosticText(message);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Read one inert own string property without invoking an accessor. */
|
|
95
|
-
function readOwnString(candidate: object, key: string): string | undefined {
|
|
96
|
-
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
97
|
-
return descriptor !== undefined &&
|
|
98
|
-
"value" in descriptor &&
|
|
99
|
-
typeof descriptor.value === "string"
|
|
100
|
-
? descriptor.value
|
|
101
|
-
: undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Escape terminal controls while retaining ordinary diagnostic wording. */
|
|
105
|
-
function escapeDiagnosticText(value: string): string {
|
|
106
|
-
const jsonBody = JSON.stringify(value).slice(1, -1);
|
|
107
|
-
return jsonBody.replace(
|
|
108
|
-
/[\u007F-\u009F\u2028\u2029]/gu,
|
|
109
|
-
character => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Quote one path after applying terminal-safe JSON-style escaping. */
|
|
114
|
-
function formatDiagnosticPath(file: string): string {
|
|
115
|
-
return `"${escapeDiagnosticText(file)}"`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Read at most one byte beyond the hard budget from one verified regular-file
|
|
120
|
-
* handle. Non-blocking open prevents a FIFO path from stalling the CI gate.
|
|
121
|
-
* @param file - Absolute candidate learnings path
|
|
122
|
-
* @param maximumBytes - Shared executable maxTokens byte ceiling
|
|
123
|
-
* @returns Strictly decoded UTF-8 content within the byte ceiling
|
|
124
|
-
*/
|
|
125
|
-
async function readBoundedRegularFile(
|
|
126
|
-
file: string,
|
|
127
|
-
maximumBytes: number
|
|
128
|
-
): Promise<string> {
|
|
129
|
-
const handle = await open(file, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
130
|
-
try {
|
|
131
|
-
const before = await handle.stat({ bigint: true });
|
|
132
|
-
if (!before.isFile()) {
|
|
133
|
-
throw new Error("unsafe input: expected a regular file");
|
|
134
|
-
}
|
|
135
|
-
if (before.size > BigInt(maximumBytes)) {
|
|
136
|
-
throw new Error(
|
|
137
|
-
`maxTokens exceeded: measured ${before.size}, allowed ${maximumBytes}`
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const buffer = Buffer.allocUnsafe(maximumBytes + 1);
|
|
142
|
-
let bytesRead = 0;
|
|
143
|
-
while (bytesRead < buffer.length) {
|
|
144
|
-
const result = await handle.read(
|
|
145
|
-
buffer,
|
|
146
|
-
bytesRead,
|
|
147
|
-
buffer.length - bytesRead,
|
|
148
|
-
null
|
|
149
|
-
);
|
|
150
|
-
if (result.bytesRead === 0) {
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
bytesRead += result.bytesRead;
|
|
154
|
-
}
|
|
155
|
-
if (bytesRead > maximumBytes) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`maxTokens exceeded: measured at least ${bytesRead}, allowed ${maximumBytes}`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const after = await handle.stat({ bigint: true });
|
|
162
|
-
if (
|
|
163
|
-
after.dev !== before.dev ||
|
|
164
|
-
after.ino !== before.ino ||
|
|
165
|
-
after.size !== before.size ||
|
|
166
|
-
after.mtimeNs !== before.mtimeNs ||
|
|
167
|
-
after.ctimeNs !== before.ctimeNs
|
|
168
|
-
) {
|
|
169
|
-
throw new Error("unsafe input: file changed during bounded read");
|
|
170
|
-
}
|
|
171
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
172
|
-
buffer.subarray(0, bytesRead)
|
|
39
|
+
`${formatDiagnosticPath(file)}: learnings budget passed (${result.entryCount}/${result.maxEntries} entries, ${result.measuredTokens}/${result.maxTokens} maxTokens)`
|
|
173
40
|
);
|
|
174
|
-
|
|
175
|
-
await handle.close();
|
|
41
|
+
return;
|
|
176
42
|
}
|
|
43
|
+
// This package script targets Lisa's own committed learnings template, which
|
|
44
|
+
// always exists — so a missing file is as much a failure here as any other
|
|
45
|
+
// violation. Host projects use the `lisa check-learnings-budget` CLI
|
|
46
|
+
// subcommand instead, which treats a missing file as an expected pass.
|
|
47
|
+
fail(`${formatDiagnosticPath(file)}: ${result.detail}`);
|
|
177
48
|
}
|
|
178
49
|
|
|
179
50
|
/**
|
|
180
|
-
* Load the
|
|
181
|
-
* output in an npm package. The `.js` source specifier keeps Bun
|
|
182
|
-
* runs aligned with TypeScript's NodeNext resolution while
|
|
183
|
-
* runtime dependency on the excluded `src` tree.
|
|
184
|
-
* @returns Canonical
|
|
51
|
+
* Load the reusable budget checker from current source in a checkout or
|
|
52
|
+
* compiled output in an npm package. The `.js` source specifier keeps Bun
|
|
53
|
+
* development runs aligned with TypeScript's NodeNext resolution while
|
|
54
|
+
* publishing no runtime dependency on the excluded `src` tree.
|
|
55
|
+
* @returns Canonical budget-checker functions
|
|
185
56
|
*/
|
|
186
|
-
async function
|
|
57
|
+
async function loadBudgetChecker(): Promise<BudgetChecker> {
|
|
187
58
|
const packageRoot = path.resolve(import.meta.dir, "..");
|
|
188
59
|
const sourceTypescript = path.join(
|
|
189
60
|
packageRoot,
|
|
190
61
|
"src",
|
|
191
62
|
"core",
|
|
192
|
-
"learnings.ts"
|
|
63
|
+
"learnings-budget-check.ts"
|
|
193
64
|
);
|
|
194
65
|
const runtimeRoot = path.join(
|
|
195
66
|
packageRoot,
|
|
196
67
|
existsSync(sourceTypescript) ? "src" : "dist",
|
|
197
68
|
"core"
|
|
198
69
|
);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
import(pathToFileURL(path.join(runtimeRoot, "learnings-entry.js")).href),
|
|
203
|
-
]);
|
|
70
|
+
const module_ = await import(
|
|
71
|
+
pathToFileURL(path.join(runtimeRoot, "learnings-budget-check.js")).href
|
|
72
|
+
);
|
|
204
73
|
return {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
renderLearningsFile: document.renderLearningsFile,
|
|
209
|
-
validateLearningEntry: entry.validateLearningEntry,
|
|
210
|
-
} as LearningsCheckerModule;
|
|
74
|
+
checkLearningsBudget: module_.checkLearningsBudget,
|
|
75
|
+
formatDiagnosticPath: module_.formatDiagnosticPath,
|
|
76
|
+
} as BudgetChecker;
|
|
211
77
|
}
|
|
212
78
|
|
|
213
79
|
/** Print one deterministic failure diagnostic and exit non-zero. */
|