@codyswann/lisa 2.219.1 → 2.220.1

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.
Files changed (61) hide show
  1. package/cdk/package-lisa/package.lisa.json +2 -2
  2. package/dist/core/learnings-document.d.ts +22 -0
  3. package/dist/core/learnings-document.d.ts.map +1 -0
  4. package/dist/core/learnings-document.js +106 -0
  5. package/dist/core/learnings-document.js.map +1 -0
  6. package/dist/core/learnings-writer.d.ts +1 -13
  7. package/dist/core/learnings-writer.d.ts.map +1 -1
  8. package/dist/core/learnings-writer.js +2 -70
  9. package/dist/core/learnings-writer.js.map +1 -1
  10. package/package.json +2 -1
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  23. package/plugins/lisa-expo-agy/plugin.json +1 -1
  24. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  28. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  34. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  39. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  44. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-rails-agy/plugin.json +1 -1
  49. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  54. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  59. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  61. package/scripts/check-learnings-budget.ts +219 -0
@@ -49,8 +49,8 @@
49
49
  },
50
50
  "defaults": {
51
51
  "dependencies": {
52
- "@aws-cdk/aws-amplify-alpha": "^2.246.0-alpha.0",
53
- "aws-cdk-lib": "2.246.0"
52
+ "@aws-cdk/aws-amplify-alpha": "^2.259.0-alpha.0",
53
+ "aws-cdk-lib": "2.259.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@codyswann/lisa": "^2.106.0"
@@ -0,0 +1,22 @@
1
+ /** Pure parsing and rendering for the canonical project-learnings document. */
2
+ import { type LearningEntry } from "./learnings-contract.js";
3
+ /**
4
+ * Render the complete file in one stable, machine-readable Markdown form.
5
+ * @param entries - Validated entries to serialize
6
+ * @returns Canonical Markdown plus JSONL document
7
+ */
8
+ export declare function renderLearningsFile(entries: readonly LearningEntry[]): string;
9
+ /**
10
+ * Parse and revalidate an existing file before it participates in a write.
11
+ * @param content - Existing learnings document
12
+ * @returns Revalidated entries from the document
13
+ */
14
+ export declare function parseLearningsFile(content: string): LearningEntry[];
15
+ /**
16
+ * Enforce the shared entry-count and model-agnostic token upper bounds.
17
+ * @param content - Canonical document
18
+ * @param entryCount - Number of entries in the document
19
+ * @param context - Error-message prefix
20
+ */
21
+ export declare function assertDocumentBudget(content: string, entryCount: number, context: string): void;
22
+ //# sourceMappingURL=learnings-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-document.d.ts","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAWjC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAsBnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAYN"}
@@ -0,0 +1,106 @@
1
+ /** Pure parsing and rendering for the canonical project-learnings document. */
2
+ import { LEARNINGS_CONTRACT, estimateLearningTokens, } from "./learnings-contract.js";
3
+ import { validateLearningEntry } from "./learnings-entry.js";
4
+ const FILE_HEADER = `# Project Learnings
5
+
6
+ <!-- lisa-learnings-contract:v${LEARNINGS_CONTRACT.version} -->
7
+
8
+ `;
9
+ const JSON_FENCE_START = "```jsonl\n";
10
+ const JSON_FENCE_END = "\n```\n";
11
+ /**
12
+ * Render the complete file in one stable, machine-readable Markdown form.
13
+ * @param entries - Validated entries to serialize
14
+ * @returns Canonical Markdown plus JSONL document
15
+ */
16
+ export function renderLearningsFile(entries) {
17
+ const lines = entries.map(entry => JSON.stringify(entry)).join("\n");
18
+ return `${FILE_HEADER}${JSON_FENCE_START}${lines}${JSON_FENCE_END}`;
19
+ }
20
+ /**
21
+ * Parse and revalidate an existing file before it participates in a write.
22
+ * @param content - Existing learnings document
23
+ * @returns Revalidated entries from the document
24
+ */
25
+ export function parseLearningsFile(content) {
26
+ if (estimateLearningTokens(content) > LEARNINGS_CONTRACT.maxTokens) {
27
+ throw new Error(`Project learnings payload exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
28
+ }
29
+ if (!content.startsWith(`${FILE_HEADER}${JSON_FENCE_START}`) ||
30
+ !content.endsWith(JSON_FENCE_END)) {
31
+ throw new Error("Invalid project learnings file format");
32
+ }
33
+ const jsonStart = FILE_HEADER.length + JSON_FENCE_START.length;
34
+ const jsonEnd = content.length - JSON_FENCE_END.length;
35
+ const entries = parseJsonLines(content.slice(jsonStart, jsonEnd)).map(validateParsedLearningEntry);
36
+ if (new Set(entries.map(entry => entry.id)).size !== entries.length) {
37
+ throw new Error("Invalid project learnings payload: duplicate ids");
38
+ }
39
+ assertDocumentBudget(content, entries.length, "Project learnings payload");
40
+ return entries;
41
+ }
42
+ /**
43
+ * Enforce the shared entry-count and model-agnostic token upper bounds.
44
+ * @param content - Canonical document
45
+ * @param entryCount - Number of entries in the document
46
+ * @param context - Error-message prefix
47
+ */
48
+ export function assertDocumentBudget(content, entryCount, context) {
49
+ if (entryCount > LEARNINGS_CONTRACT.maxEntries) {
50
+ throw new Error(`${context} exceeds maxEntries: measured ${entryCount}, allowed ${LEARNINGS_CONTRACT.maxEntries}`);
51
+ }
52
+ const estimatedTokens = estimateLearningTokens(content);
53
+ if (estimatedTokens > LEARNINGS_CONTRACT.maxTokens) {
54
+ throw new Error(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
55
+ }
56
+ }
57
+ /**
58
+ * Add a stable entry identifier to validation failures.
59
+ * @param candidate - Parsed JSONL value
60
+ * @param index - Zero-based JSONL entry index
61
+ * @returns Validated learning entry
62
+ */
63
+ function validateParsedLearningEntry(candidate, index) {
64
+ try {
65
+ return validateLearningEntry(candidate);
66
+ }
67
+ catch (error) {
68
+ const id = readDiagnosticEntryId(candidate);
69
+ const label = id === undefined ? `entry ${index + 1}` : `entry ${JSON.stringify(id)}`;
70
+ const detail = error instanceof Error ? error.message : String(error);
71
+ throw new Error(`Invalid learning ${label}: ${detail}`);
72
+ }
73
+ }
74
+ /**
75
+ * Read a parsed entry id only when it is an inert own data property.
76
+ * @param candidate - Parsed JSONL value
77
+ * @returns Safe diagnostic id, when present
78
+ */
79
+ function readDiagnosticEntryId(candidate) {
80
+ if (candidate === null || typeof candidate !== "object") {
81
+ return undefined;
82
+ }
83
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, "id");
84
+ return descriptor !== undefined &&
85
+ "value" in descriptor &&
86
+ typeof descriptor.value === "string"
87
+ ? descriptor.value
88
+ : undefined;
89
+ }
90
+ /**
91
+ * Parse the strict JSONL payload with a stable, user-facing error.
92
+ * @param payload - JSONL payload
93
+ * @returns Parsed unknown entry values
94
+ */
95
+ function parseJsonLines(payload) {
96
+ if (payload === "") {
97
+ return [];
98
+ }
99
+ try {
100
+ return payload.split("\n").map(line => JSON.parse(line));
101
+ }
102
+ catch {
103
+ throw new Error("Invalid project learnings JSONL payload");
104
+ }
105
+ }
106
+ //# sourceMappingURL=learnings-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-document.js","sourceRoot":"","sources":["../../src/core/learnings-document.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,3 @@
1
- import { type LearningEntry } from "./learnings-contract.js";
2
1
  /**
3
2
  * Persist one already-selected learning without generating or truncating it.
4
3
  * @param projectRoot - Absolute path to the host project root
@@ -6,17 +5,6 @@ import { type LearningEntry } from "./learnings-contract.js";
6
5
  * @returns Absolute path to the persisted learnings file
7
6
  */
8
7
  export declare function persistLearningEntry(projectRoot: string, candidate: unknown): Promise<string>;
9
- /**
10
- * Render the complete file in one stable, machine-readable Markdown form.
11
- * @param entries - Validated entries to serialize
12
- * @returns Canonical Markdown plus JSONL document
13
- */
14
- export declare function renderLearningsFile(entries: readonly LearningEntry[]): string;
15
- /**
16
- * Parse and revalidate an existing file before it participates in a write.
17
- * @param content - Existing learnings document
18
- * @returns Revalidated entries from the document
19
- */
20
- export declare function parseLearningsFile(content: string): LearningEntry[];
8
+ export { parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
21
9
  export { validateLearningEntry } from "./learnings-entry.js";
22
10
  //# sourceMappingURL=learnings-writer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"learnings-writer.d.ts","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAqBjC;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAsBnE;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"learnings-writer.d.ts","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -2,18 +2,11 @@
2
2
  import * as fse from "fs-extra";
3
3
  import { rename, writeFile } from "node:fs/promises";
4
4
  import * as path from "node:path";
5
- import { LEARNINGS_CONTRACT, estimateLearningTokens, } from "./learnings-contract.js";
5
+ import { assertDocumentBudget, parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
6
6
  import { validateLearningEntry } from "./learnings-entry.js";
7
7
  import { assertSafeLearningParents, readExistingLearnings, resolveSafeLearningTarget, } from "./learnings-file-safety.js";
8
8
  import { withLearningTargetLock } from "./learnings-lock.js";
9
9
  import { readProjectConfig, resolveProjectLearningsFile, } from "./project-config.js";
10
- const FILE_HEADER = `# Project Learnings
11
-
12
- <!-- lisa-learnings-contract:v${LEARNINGS_CONTRACT.version} -->
13
-
14
- `;
15
- const JSON_FENCE_START = "```jsonl\n";
16
- const JSON_FENCE_END = "\n```\n";
17
10
  /**
18
11
  * Persist one already-selected learning without generating or truncating it.
19
12
  * @param projectRoot - Absolute path to the host project root
@@ -47,37 +40,7 @@ export async function persistLearningEntry(projectRoot, candidate) {
47
40
  return target;
48
41
  });
49
42
  }
50
- /**
51
- * Render the complete file in one stable, machine-readable Markdown form.
52
- * @param entries - Validated entries to serialize
53
- * @returns Canonical Markdown plus JSONL document
54
- */
55
- export function renderLearningsFile(entries) {
56
- const lines = entries.map(entry => JSON.stringify(entry)).join("\n");
57
- return `${FILE_HEADER}${JSON_FENCE_START}${lines}${JSON_FENCE_END}`;
58
- }
59
- /**
60
- * Parse and revalidate an existing file before it participates in a write.
61
- * @param content - Existing learnings document
62
- * @returns Revalidated entries from the document
63
- */
64
- export function parseLearningsFile(content) {
65
- if (estimateLearningTokens(content) > LEARNINGS_CONTRACT.maxTokens) {
66
- throw new Error(`Project learnings payload exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
67
- }
68
- if (!content.startsWith(`${FILE_HEADER}${JSON_FENCE_START}`) ||
69
- !content.endsWith(JSON_FENCE_END)) {
70
- throw new Error("Invalid project learnings file format");
71
- }
72
- const jsonStart = FILE_HEADER.length + JSON_FENCE_START.length;
73
- const jsonEnd = content.length - JSON_FENCE_END.length;
74
- const entries = parseJsonLines(content.slice(jsonStart, jsonEnd)).map(validateLearningEntry);
75
- if (new Set(entries.map(entry => entry.id)).size !== entries.length) {
76
- throw new Error("Invalid project learnings payload: duplicate ids");
77
- }
78
- assertDocumentBudget(content, entries.length, "Project learnings payload");
79
- return entries;
80
- }
43
+ export { parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
81
44
  export { validateLearningEntry } from "./learnings-entry.js";
82
45
  /**
83
46
  * Build and budget-check the next canonical document.
@@ -94,35 +57,4 @@ function buildNextDocument(entries, entry) {
94
57
  assertDocumentBudget(rendered, nextEntries.length, "Learnings file");
95
58
  return rendered;
96
59
  }
97
- /**
98
- * Enforce the shared entry-count and model-agnostic token upper bounds.
99
- * @param content - Canonical document
100
- * @param entryCount - Number of entries in the document
101
- * @param context - Error-message prefix
102
- */
103
- function assertDocumentBudget(content, entryCount, context) {
104
- if (entryCount > LEARNINGS_CONTRACT.maxEntries) {
105
- throw new Error(`${context} exceeds maxEntries ${LEARNINGS_CONTRACT.maxEntries}`);
106
- }
107
- const estimatedTokens = estimateLearningTokens(content);
108
- if (estimatedTokens > LEARNINGS_CONTRACT.maxTokens) {
109
- throw new Error(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
110
- }
111
- }
112
- /**
113
- * Parse the strict JSONL payload with a stable, user-facing error.
114
- * @param payload - JSONL payload
115
- * @returns Parsed unknown entry values
116
- */
117
- function parseJsonLines(payload) {
118
- if (payload === "") {
119
- return [];
120
- }
121
- try {
122
- return payload.split("\n").map(line => JSON.parse(line));
123
- }
124
- catch {
125
- throw new Error("Invalid project learnings JSONL payload");
126
- }
127
- }
128
60
  //# sourceMappingURL=learnings-writer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"learnings-writer.js","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"learnings-writer.js","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -31,6 +31,7 @@
31
31
  "lisa:commit-and-pr:local": "bash scripts/lisa-commit-and-pr-local.sh",
32
32
  "prepublishOnly": "$npm_execpath run build",
33
33
  "check:plugins": "bash scripts/check-plugins-sync.sh",
34
+ "check:learnings-budget": "bun scripts/check-learnings-budget.ts",
34
35
  "check:rules-pairing": "bash scripts/check-rules-pairing.sh",
35
36
  "check:workflow-inputs": "node scripts/detect-stale-workflow-inputs.mjs --project . --project typescript/create-only --project rails/create-only --json"
36
37
  },
@@ -101,7 +102,7 @@
101
102
  "form-data": ">=4.0.6"
102
103
  },
103
104
  "name": "@codyswann/lisa",
104
- "version": "2.219.1",
105
+ "version": "2.220.1",
105
106
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
106
107
  "main": "dist/index.js",
107
108
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
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.219.1",
3
+ "version": "2.220.1",
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.219.1",
3
+ "version": "2.220.1",
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.219.1",
3
+ "version": "2.220.1",
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.219.1",
3
+ "version": "2.220.1",
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-phaser",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.219.1",
3
+ "version": "2.220.1",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,219 @@
1
+ /** CI gate for the canonical project learnings document and its hard budgets. */
2
+ import { constants, existsSync } from "node:fs";
3
+ import { open } from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import type * as LearningsModule from "@codyswann/lisa/learnings";
7
+
8
+ type LearningsCheckerModule = Pick<
9
+ typeof LearningsModule,
10
+ | "LEARNINGS_CONTRACT"
11
+ | "estimateLearningTokens"
12
+ | "parseLearningsFile"
13
+ | "renderLearningsFile"
14
+ | "validateLearningEntry"
15
+ >;
16
+
17
+ const DEFAULT_LEARNINGS_FILE = path.resolve(
18
+ import.meta.dir,
19
+ "..",
20
+ "all",
21
+ "create-only",
22
+ ".claude",
23
+ "rules",
24
+ "PROJECT_LEARNINGS.md"
25
+ );
26
+
27
+ /** Run the package-facing checker with zero or one explicit file path. */
28
+ async function main(): Promise<void> {
29
+ const arguments_ = process.argv.slice(2);
30
+ if (arguments_.length > 1) {
31
+ fail("Usage: bun run check:learnings-budget -- [PROJECT_LEARNINGS.md]");
32
+ }
33
+
34
+ const file =
35
+ arguments_.length === 0
36
+ ? DEFAULT_LEARNINGS_FILE
37
+ : path.resolve(process.cwd(), arguments_[0] as string);
38
+
39
+ try {
40
+ const {
41
+ LEARNINGS_CONTRACT,
42
+ estimateLearningTokens,
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
+
66
+ console.log(
67
+ `${formatDiagnosticPath(file)}: learnings budget passed (${entries.length}/${LEARNINGS_CONTRACT.maxEntries} entries, ${measuredTokens}/${LEARNINGS_CONTRACT.maxTokens} maxTokens)`
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)
173
+ );
174
+ } finally {
175
+ await handle.close();
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Load the executable contract from current source in a checkout or compiled
181
+ * output in an npm package. The `.js` source specifier keeps Bun development
182
+ * runs aligned with TypeScript's NodeNext resolution while publishing no
183
+ * runtime dependency on the excluded `src` tree.
184
+ * @returns Canonical executable learnings module
185
+ */
186
+ async function loadLearningsModule(): Promise<LearningsCheckerModule> {
187
+ const packageRoot = path.resolve(import.meta.dir, "..");
188
+ const sourceTypescript = path.join(
189
+ packageRoot,
190
+ "src",
191
+ "core",
192
+ "learnings.ts"
193
+ );
194
+ const runtimeRoot = path.join(
195
+ packageRoot,
196
+ existsSync(sourceTypescript) ? "src" : "dist",
197
+ "core"
198
+ );
199
+ const [contract, document, entry] = await Promise.all([
200
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-contract.js")).href),
201
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-document.js")).href),
202
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-entry.js")).href),
203
+ ]);
204
+ return {
205
+ LEARNINGS_CONTRACT: contract.LEARNINGS_CONTRACT,
206
+ estimateLearningTokens: contract.estimateLearningTokens,
207
+ parseLearningsFile: document.parseLearningsFile,
208
+ renderLearningsFile: document.renderLearningsFile,
209
+ validateLearningEntry: entry.validateLearningEntry,
210
+ } as LearningsCheckerModule;
211
+ }
212
+
213
+ /** Print one deterministic failure diagnostic and exit non-zero. */
214
+ function fail(message: string): never {
215
+ console.error(`check:learnings-budget: ${message}`);
216
+ process.exit(1);
217
+ }
218
+
219
+ await main();