litopencode 0.0.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.
Files changed (55) hide show
  1. package/README.md +124 -0
  2. package/bin/litopencode +4 -0
  3. package/cover.png +0 -0
  4. package/dist/agents/defaults.d.ts +15 -0
  5. package/dist/agents/defaults.js +42 -0
  6. package/dist/agents/registry.d.ts +64 -0
  7. package/dist/agents/registry.js +31 -0
  8. package/dist/agents/specialists.d.ts +49 -0
  9. package/dist/agents/specialists.js +198 -0
  10. package/dist/agents/types.d.ts +30 -0
  11. package/dist/agents/types.js +4 -0
  12. package/dist/agents.d.ts +4 -0
  13. package/dist/agents.js +3 -0
  14. package/dist/cli.d.ts +8 -0
  15. package/dist/cli.js +204 -0
  16. package/dist/commands.d.ts +30 -0
  17. package/dist/commands.js +59 -0
  18. package/dist/config.d.ts +16 -0
  19. package/dist/config.js +61 -0
  20. package/dist/features.d.ts +108 -0
  21. package/dist/features.js +161 -0
  22. package/dist/hooks.d.ts +6 -0
  23. package/dist/hooks.js +11 -0
  24. package/dist/index.d.ts +15 -0
  25. package/dist/index.js +45 -0
  26. package/dist/ledger.d.ts +39 -0
  27. package/dist/ledger.js +147 -0
  28. package/dist/logger.d.ts +9 -0
  29. package/dist/logger.js +21 -0
  30. package/dist/skills.d.ts +47 -0
  31. package/dist/skills.js +60 -0
  32. package/dist/state.d.ts +14 -0
  33. package/dist/state.js +20 -0
  34. package/dist/tool-guards.d.ts +24 -0
  35. package/dist/tool-guards.js +82 -0
  36. package/dist/tools.d.ts +19 -0
  37. package/dist/tools.js +134 -0
  38. package/docs/migration.md +51 -0
  39. package/docs/release-checklist.md +120 -0
  40. package/generate_cover.py +127 -0
  41. package/package.json +29 -0
  42. package/skills/agent-roster/SKILL.md +26 -0
  43. package/skills/doctor-installer/SKILL.md +22 -0
  44. package/skills/durable-litgoal/SKILL.md +22 -0
  45. package/skills/litwork-activation/SKILL.md +25 -0
  46. package/skills/release-guardrails/SKILL.md +23 -0
  47. package/skills/tool-guards/SKILL.md +22 -0
  48. package/skills/workflow-loop/SKILL.md +24 -0
  49. package/tools/check-pack-payload.mjs +156 -0
  50. package/tools/check-version-lockstep.mjs +144 -0
  51. package/tools/run-build.mjs +34 -0
  52. package/tools/run-typecheck.mjs +25 -0
  53. package/tools/scan-legacy-tokens.mjs +211 -0
  54. package/tsconfig.build.json +12 -0
  55. package/tsconfig.json +13 -0
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+
6
+ class VersionLockstepError extends Error {
7
+ constructor(message, exitCode) {
8
+ super(message);
9
+ this.name = "VersionLockstepError";
10
+ this.exitCode = exitCode;
11
+ }
12
+ }
13
+
14
+ function parseArgs(argv) {
15
+ const options = {
16
+ packagePath: "package.json",
17
+ lockfilePath: "package-lock.json"
18
+ };
19
+
20
+ for (let index = 0; index < argv.length; index += 1) {
21
+ const arg = argv[index];
22
+ const next = argv[index + 1];
23
+ if (arg === "--package" && next !== undefined) {
24
+ options.packagePath = next;
25
+ index += 1;
26
+ } else if (arg === "--lockfile" && next !== undefined) {
27
+ options.lockfilePath = next;
28
+ index += 1;
29
+ } else {
30
+ throw new VersionLockstepError(`unknown or incomplete argument: ${arg}`, 2);
31
+ }
32
+ }
33
+
34
+ return options;
35
+ }
36
+
37
+ async function readJson(filePath, label) {
38
+ try {
39
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
40
+ } catch (error) {
41
+ if (label === "package-lock.json" && error && error.code === "ENOENT") {
42
+ return undefined;
43
+ }
44
+ const detail = error instanceof Error ? error.message : String(error);
45
+ throw new VersionLockstepError(`cannot read or parse ${label}: ${detail}`, 2);
46
+ }
47
+ }
48
+
49
+ function requireObject(value, label) {
50
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
51
+ throw new VersionLockstepError(`${label} must be a JSON object`, 2);
52
+ }
53
+ return value;
54
+ }
55
+
56
+ function requireString(object, field, label) {
57
+ const value = object[field];
58
+ if (typeof value !== "string" || value.trim() === "") {
59
+ throw new VersionLockstepError(`${label} ${field} must be a non-empty string`, 2);
60
+ }
61
+ return value;
62
+ }
63
+
64
+ function readPackageIdentity(parsedPackage) {
65
+ const packageObject = requireObject(parsedPackage, "package.json");
66
+ return {
67
+ name: requireString(packageObject, "name", "package.json"),
68
+ version: requireString(packageObject, "version", "package.json")
69
+ };
70
+ }
71
+
72
+ function readLockfileIdentity(parsedLockfile) {
73
+ if (parsedLockfile === undefined) {
74
+ return undefined;
75
+ }
76
+ const lockfileObject = requireObject(parsedLockfile, "package-lock.json");
77
+ return {
78
+ name: requireString(lockfileObject, "name", "package-lock.json"),
79
+ version: requireString(lockfileObject, "version", "package-lock.json"),
80
+ packages: lockfileObject.packages
81
+ };
82
+ }
83
+
84
+ function readRootPackageEntry(packages) {
85
+ if (packages === undefined) {
86
+ return undefined;
87
+ }
88
+ const packagesObject = requireObject(packages, "package-lock.json packages");
89
+ const rootEntry = packagesObject[""];
90
+ if (rootEntry === undefined) {
91
+ return undefined;
92
+ }
93
+ const rootObject = requireObject(rootEntry, 'package-lock.json packages[""]');
94
+ return {
95
+ name: requireString(rootObject, "name", 'package-lock.json packages[""]'),
96
+ version: requireString(rootObject, "version", 'package-lock.json packages[""]')
97
+ };
98
+ }
99
+
100
+ function assertEqual(actual, expected, label) {
101
+ if (actual !== expected) {
102
+ throw new VersionLockstepError(`${label} mismatch: expected ${expected}, found ${actual}`, 1);
103
+ }
104
+ }
105
+
106
+ async function main() {
107
+ try {
108
+ const options = parseArgs(process.argv.slice(2));
109
+ const packagePath = path.resolve(options.packagePath);
110
+ const lockfilePath = path.resolve(options.lockfilePath);
111
+ const [parsedPackage, parsedLockfile] = await Promise.all([
112
+ readJson(packagePath, "package.json"),
113
+ readJson(lockfilePath, "package-lock.json")
114
+ ]);
115
+ const packageIdentity = readPackageIdentity(parsedPackage);
116
+ const lockfileIdentity = readLockfileIdentity(parsedLockfile);
117
+ if (lockfileIdentity === undefined) {
118
+ console.log(`version lockstep skipped: package-lock.json not present for ${packageIdentity.name}@${packageIdentity.version}`);
119
+ return;
120
+ }
121
+ const rootEntryIdentity = readRootPackageEntry(lockfileIdentity.packages);
122
+
123
+ assertEqual(lockfileIdentity.name, packageIdentity.name, "lockfile root name");
124
+ assertEqual(lockfileIdentity.version, packageIdentity.version, "lockfile root version");
125
+
126
+ if (rootEntryIdentity !== undefined) {
127
+ assertEqual(rootEntryIdentity.name, packageIdentity.name, 'lockfile packages[""] name');
128
+ assertEqual(rootEntryIdentity.version, packageIdentity.version, 'lockfile packages[""] version');
129
+ }
130
+
131
+ console.log(`version lockstep passed: ${packageIdentity.name}@${packageIdentity.version}`);
132
+ } catch (error) {
133
+ if (error instanceof VersionLockstepError) {
134
+ console.error(`version lockstep failed: ${error.message}`);
135
+ process.exitCode = error.exitCode;
136
+ return;
137
+ }
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ console.error(`version lockstep failed: ${message}`);
140
+ process.exitCode = 2;
141
+ }
142
+ }
143
+
144
+ await main();
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import process from "node:process";
5
+
6
+ const tscBin = "node_modules/typescript/bin/tsc";
7
+ const sourceEntry = "src/index.ts";
8
+ const builtEntry = "dist/index.js";
9
+
10
+ if (!fs.existsSync(sourceEntry)) {
11
+ if (fs.existsSync(builtEntry)) {
12
+ console.log("build skipped: source files are not present in this packed artifact.");
13
+ process.exit(0);
14
+ }
15
+ console.error("build failed: source files are missing and no built dist artifact exists.");
16
+ process.exit(1);
17
+ }
18
+
19
+ if (!fs.existsSync(tscBin)) {
20
+ if (fs.existsSync(builtEntry)) {
21
+ console.log("build skipped: TypeScript dev dependency is not installed and dist already exists.");
22
+ process.exit(0);
23
+ }
24
+ console.error("build failed: TypeScript dev dependency is not installed.");
25
+ process.exit(1);
26
+ }
27
+
28
+ const result = spawnSync(process.execPath, [tscBin, "-p", "tsconfig.build.json"], {
29
+ cwd: process.cwd(),
30
+ encoding: "utf8",
31
+ stdio: "inherit"
32
+ });
33
+
34
+ process.exit(result.status ?? 1);
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import process from "node:process";
5
+
6
+ const tscBin = "node_modules/typescript/bin/tsc";
7
+ const sourceEntry = "src/index.ts";
8
+
9
+ if (!fs.existsSync(tscBin)) {
10
+ console.log("typecheck skipped: TypeScript dev dependency is not installed in this packed artifact.");
11
+ process.exit(0);
12
+ }
13
+
14
+ if (!fs.existsSync(sourceEntry)) {
15
+ console.log("typecheck skipped: TypeScript source files are not present in this packed artifact.");
16
+ process.exit(0);
17
+ }
18
+
19
+ const result = spawnSync(process.execPath, [tscBin, "--noEmit"], {
20
+ cwd: process.cwd(),
21
+ encoding: "utf8",
22
+ stdio: "inherit"
23
+ });
24
+
25
+ process.exit(result.status ?? 1);
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+
6
+ const LEGACY_TOKEN_PARTS = [
7
+ ["om", "o"],
8
+ ["oh-my-open", "agent"],
9
+ ["oh-my-open", "code"],
10
+ ["sisyphus", "labs"],
11
+ ["lazy", "codex"],
12
+ ["lazy", "claude"],
13
+ ["code-yeong", "yu"],
14
+ ["u", "lw"],
15
+ ["ultra", "work"],
16
+ ["ultra", "goal"]
17
+ ];
18
+ const LEGACY_TOKENS = LEGACY_TOKEN_PARTS.map((parts) => parts.join(""));
19
+ const TOKEN_PATTERN = new RegExp(
20
+ `(?<![A-Za-z0-9_])(${LEGACY_TOKENS.map(escapeRegex).join("|")})(?![A-Za-z0-9_])`,
21
+ "giu"
22
+ );
23
+ const SKIPPED_DIRS = new Set([".git", ".litcodex", ".litopencode", "node_modules", "# REFERENCE"]);
24
+ const SKIPPED_FILES = new Set(["tools/scan-legacy-tokens.mjs", "tools/legacy-token-allowlist.json"]);
25
+ const SKIPPED_EXTENSIONS = [".tgz", ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z"];
26
+
27
+ class AllowlistError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "AllowlistError";
31
+ }
32
+ }
33
+
34
+ function escapeRegex(value) {
35
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
36
+ }
37
+
38
+ function parseArgs(argv) {
39
+ const options = {
40
+ root: process.cwd(),
41
+ allowlist: "tools/legacy-token-allowlist.json"
42
+ };
43
+
44
+ for (let index = 0; index < argv.length; index += 1) {
45
+ const arg = argv[index];
46
+ const next = argv[index + 1];
47
+ if (arg === "--root" && next !== undefined) {
48
+ options.root = next;
49
+ index += 1;
50
+ } else if (arg === "--allowlist" && next !== undefined) {
51
+ options.allowlist = next;
52
+ index += 1;
53
+ } else {
54
+ throw new AllowlistError(`unknown or incomplete argument: ${arg}`);
55
+ }
56
+ }
57
+
58
+ return options;
59
+ }
60
+
61
+ function normalizeRelativePath(value) {
62
+ return value.split(path.sep).join("/");
63
+ }
64
+
65
+ function isSkippedFile(relativePath, extraSkippedFiles) {
66
+ if (SKIPPED_FILES.has(relativePath) || extraSkippedFiles.has(relativePath)) {
67
+ return true;
68
+ }
69
+ if (relativePath === "pack-report.json" || relativePath.endsWith(".pack-report.json")) {
70
+ return true;
71
+ }
72
+ return SKIPPED_EXTENSIONS.some((extension) => relativePath.endsWith(extension));
73
+ }
74
+
75
+ function requireString(entry, field, index) {
76
+ const value = entry[field];
77
+ if (typeof value !== "string" || value.trim() === "") {
78
+ throw new AllowlistError(`entries[${index}].${field} must be a non-empty string`);
79
+ }
80
+ return value;
81
+ }
82
+
83
+ async function readAllowlist(allowlistPath) {
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(await fs.readFile(allowlistPath, "utf8"));
87
+ } catch (error) {
88
+ if (error && error.code === "ENOENT") {
89
+ return [];
90
+ }
91
+ throw new AllowlistError(`cannot read or parse allowlist: ${error.message}`);
92
+ }
93
+
94
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.entries)) {
95
+ throw new AllowlistError("allowlist must be an object with an entries array");
96
+ }
97
+
98
+ return parsed.entries.map((entry, index) => {
99
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
100
+ throw new AllowlistError(`entries[${index}] must be an object`);
101
+ }
102
+ const token = requireString(entry, "token", index).toLowerCase();
103
+ if (!LEGACY_TOKENS.includes(token)) {
104
+ throw new AllowlistError(`entries[${index}].token must be one of: ${LEGACY_TOKENS.join(", ")}`);
105
+ }
106
+ return {
107
+ path: normalizeRelativePath(requireString(entry, "path", index)),
108
+ token,
109
+ reason: requireString(entry, "reason", index),
110
+ removalCondition: requireString(entry, "removalCondition", index),
111
+ match: requireString(entry, "match", index)
112
+ };
113
+ });
114
+ }
115
+
116
+ function isAllowed(match, allowlist) {
117
+ return allowlist.some((entry) => (
118
+ entry.path === match.path
119
+ && entry.token === match.token
120
+ && entry.match === match.line
121
+ ));
122
+ }
123
+
124
+ async function collectFiles(root, extraSkippedFiles, current = root) {
125
+ const entries = await fs.readdir(current, { withFileTypes: true });
126
+ const files = [];
127
+ for (const entry of entries) {
128
+ if (entry.isDirectory() && SKIPPED_DIRS.has(entry.name)) {
129
+ continue;
130
+ }
131
+ const fullPath = path.join(current, entry.name);
132
+ const relativePath = normalizeRelativePath(path.relative(root, fullPath));
133
+ if (entry.isDirectory()) {
134
+ files.push(...await collectFiles(root, extraSkippedFiles, fullPath));
135
+ } else if (entry.isFile() && !isSkippedFile(relativePath, extraSkippedFiles)) {
136
+ files.push({ fullPath, relativePath });
137
+ }
138
+ }
139
+ return files;
140
+ }
141
+
142
+ function scanText(relativePath, text) {
143
+ if (text.includes("\u0000")) {
144
+ return [];
145
+ }
146
+
147
+ const matches = [];
148
+ const lines = text.split(/\r?\n/);
149
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
150
+ const line = lines[lineIndex];
151
+ for (const tokenMatch of line.matchAll(TOKEN_PATTERN)) {
152
+ matches.push({
153
+ path: relativePath,
154
+ lineNumber: lineIndex + 1,
155
+ token: tokenMatch[1].toLowerCase(),
156
+ line
157
+ });
158
+ }
159
+ }
160
+ return matches;
161
+ }
162
+
163
+ async function scan(root, allowlist, extraSkippedFiles) {
164
+ const files = await collectFiles(root, extraSkippedFiles);
165
+ const matches = [];
166
+ for (const file of files) {
167
+ const text = await fs.readFile(file.fullPath, "utf8");
168
+ matches.push(...scanText(file.relativePath, text));
169
+ }
170
+
171
+ const unallowlisted = matches.filter((match) => !isAllowed(match, allowlist));
172
+ return {
173
+ allowedCount: matches.length - unallowlisted.length,
174
+ unallowlisted
175
+ };
176
+ }
177
+
178
+ function printMatches(matches) {
179
+ for (const match of matches) {
180
+ console.log(`${match.path}:${match.lineNumber}: ${match.line}`);
181
+ }
182
+ }
183
+
184
+ async function main() {
185
+ try {
186
+ const options = parseArgs(process.argv.slice(2));
187
+ const root = path.resolve(options.root);
188
+ const allowlistPath = path.isAbsolute(options.allowlist)
189
+ ? options.allowlist
190
+ : path.resolve(root, options.allowlist);
191
+ const allowlist = await readAllowlist(allowlistPath);
192
+ const allowlistRelativePath = normalizeRelativePath(path.relative(root, allowlistPath));
193
+ const result = await scan(root, allowlist, new Set([allowlistRelativePath]));
194
+
195
+ if (result.unallowlisted.length > 0) {
196
+ console.log(`legacy-token scan failed: ${result.unallowlisted.length} unallowlisted match(es)`);
197
+ printMatches(result.unallowlisted);
198
+ process.exitCode = 1;
199
+ return;
200
+ }
201
+
202
+ const suffix = result.allowedCount === 1 ? "match" : "matches";
203
+ console.log(`legacy-token scan passed: allowed ${result.allowedCount} ${suffix}`);
204
+ } catch (error) {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ console.error(`legacy-token allowlist error: ${message}`);
207
+ process.exitCode = 2;
208
+ }
209
+ }
210
+
211
+ await main();
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "declaration": true,
6
+ "emitDeclarationOnly": false,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "rewriteRelativeImportExtensions": true
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "allowImportingTsExtensions": true,
9
+ "verbatimModuleSyntax": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }