@draig/lexis-two 1.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.
- package/.agents/plugins/marketplace.json +21 -0
- package/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +9 -0
- package/.clinerules/lexis-two.md +163 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.cursor/rules/lexis-two.mdc +169 -0
- package/.env.example +8 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/copilot-instructions.md +47 -0
- package/.github/plugin/marketplace.json +20 -0
- package/.github/plugin/plugin.json +16 -0
- package/.github/workflows/deploy-site.yml +53 -0
- package/.github/workflows/test.yml +29 -0
- package/.kiro/steering/lexis-two.md +167 -0
- package/.nojekyll +0 -0
- package/.opencode/command/lexis-two-audit.md +5 -0
- package/.opencode/command/lexis-two-debt.md +5 -0
- package/.opencode/command/lexis-two-help.md +5 -0
- package/.opencode/command/lexis-two-plan.md +5 -0
- package/.opencode/command/lexis-two-review.md +5 -0
- package/.opencode/command/lexis-two-security.md +5 -0
- package/.opencode/command/lexis-two.md +5 -0
- package/.opencode/plugins/lexis-two.mjs +74 -0
- package/.windsurf/rules/lexis-two.md +163 -0
- package/AGENTS.md +163 -0
- package/AUDIT.md +74 -0
- package/CNAME +1 -0
- package/LICENSE +23 -0
- package/README.md +301 -0
- package/SPECXIS.md +576 -0
- package/assets/benchmark-3model.svg +21 -0
- package/assets/lexis-two-complete.webp +0 -0
- package/assets/lexis-two-nobg.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/social-preview.png +0 -0
- package/benchmarks/README.md +114 -0
- package/benchmarks/arms/baseline.js +2 -0
- package/benchmarks/arms/caveman-SKILL.md +67 -0
- package/benchmarks/arms/caveman.js +8 -0
- package/benchmarks/arms/lexis-two.js +10 -0
- package/benchmarks/arms/ponytail.js +6 -0
- package/benchmarks/behavior.js +58 -0
- package/benchmarks/behavior.yaml +40 -0
- package/benchmarks/benchmark-local.py +156 -0
- package/benchmarks/benchmark-opencode-go.js +294 -0
- package/benchmarks/correctness.js +294 -0
- package/benchmarks/lib/aggregate-opencode-go.js +103 -0
- package/benchmarks/lib/load-env.js +31 -0
- package/benchmarks/lib/opencode-go-client.js +151 -0
- package/benchmarks/loc.js +13 -0
- package/benchmarks/opencode-go-models.json +31 -0
- package/benchmarks/promptfooconfig.yaml +41 -0
- package/benchmarks/prompts.json +15 -0
- package/benchmarks/render-opencode-go-report.js +28 -0
- package/benchmarks/results/2026-06-15-llama3.2-local.md +76 -0
- package/benchmarks/results/2026-06-16-opencode-go.md +56 -0
- package/benchmarks/results/opencode-go-2026-06-16-report.html +226 -0
- package/benchmarks/results/opencode-go-2026-06-16.json +1339 -0
- package/commands/lexis-two-audit.toml +3 -0
- package/commands/lexis-two-debt.toml +3 -0
- package/commands/lexis-two-help.toml +3 -0
- package/commands/lexis-two-plan.toml +3 -0
- package/commands/lexis-two-review.toml +3 -0
- package/commands/lexis-two-security.toml +3 -0
- package/commands/lexis-two.toml +3 -0
- package/docs/assets/lexis-two-nobg.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/assets/logo.svg +4 -0
- package/docs/portability.md +147 -0
- package/docs/site.md +52 -0
- package/examples/api-endpoint.md +68 -0
- package/examples/caching.md +74 -0
- package/examples/date-picker.md +48 -0
- package/examples/email-validation.md +51 -0
- package/examples/sorting.md +42 -0
- package/gemini-extension.json +7 -0
- package/hooks/copilot-hooks.json +21 -0
- package/hooks/hooks.json +31 -0
- package/hooks/lexis-two-activate.js +72 -0
- package/hooks/lexis-two-config.js +101 -0
- package/hooks/lexis-two-instructions.js +126 -0
- package/hooks/lexis-two-mode-tracker.js +55 -0
- package/hooks/lexis-two-runtime.js +50 -0
- package/hooks/lexis-two-statusline.ps1 +19 -0
- package/hooks/lexis-two-statusline.sh +11 -0
- package/opencode.json +4 -0
- package/package.json +31 -0
- package/pi-extension/index.js +161 -0
- package/pi-extension/package.json +8 -0
- package/pi-extension/test/extension.test.js +89 -0
- package/pi-extension/test/helpers.test.js +35 -0
- package/scripts/check-rule-copies.js +82 -0
- package/site/astro.config.mjs +18 -0
- package/site/package-lock.json +4913 -0
- package/site/package.json +14 -0
- package/site/public/CNAME +1 -0
- package/site/public/assets/lexis-two-nobg.png +0 -0
- package/site/public/assets/logo.png +0 -0
- package/site/public/assets/logo.svg +4 -0
- package/site/public/robots.txt +4 -0
- package/site/src/components/Adapt.astro +33 -0
- package/site/src/components/Benchmarks.astro +232 -0
- package/site/src/components/Commands.astro +33 -0
- package/site/src/components/Ecosystem.astro +30 -0
- package/site/src/components/Example.astro +77 -0
- package/site/src/components/Footer.astro +28 -0
- package/site/src/components/Header.astro +87 -0
- package/site/src/components/Hero.astro +58 -0
- package/site/src/components/Home.astro +46 -0
- package/site/src/components/Hosts.astro +62 -0
- package/site/src/components/Install.astro +143 -0
- package/site/src/components/LanguageSwitcher.astro +82 -0
- package/site/src/components/Philosophy.astro +23 -0
- package/site/src/components/Stacks.astro +33 -0
- package/site/src/components/Suggested.astro +39 -0
- package/site/src/data/opencode-go-benchmark.json +230 -0
- package/site/src/i18n/en.ts +155 -0
- package/site/src/i18n/es.ts +158 -0
- package/site/src/i18n/index.ts +14 -0
- package/site/src/layouts/Layout.astro +114 -0
- package/site/src/pages/benchmarks.astro +4 -0
- package/site/src/pages/es/benchmarks.astro +4 -0
- package/site/src/pages/es/index.astro +10 -0
- package/site/src/pages/index.astro +10 -0
- package/site/src/styles/global.css +780 -0
- package/site/tsconfig.json +3 -0
- package/skills/lexis-two/SKILL.md +109 -0
- package/skills/lexis-two-audit/SKILL.md +21 -0
- package/skills/lexis-two-debt/SKILL.md +22 -0
- package/skills/lexis-two-plan/SKILL.md +25 -0
- package/skills/lexis-two-review/SKILL.md +24 -0
- package/skills/lexis-two-security/SKILL.md +24 -0
- package/tests/behavior.test.js +80 -0
- package/tests/commands.test.js +40 -0
- package/tests/copilot-plugin.test.js +33 -0
- package/tests/correctness.test.js +191 -0
- package/tests/gemini-extension.test.js +78 -0
- package/tests/hooks-windows.test.js +48 -0
- package/tests/hooks.test.js +177 -0
- package/tests/opencode-plugin.test.js +64 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// lexis-two — shared configuration resolver
|
|
2
|
+
//
|
|
3
|
+
// Resolution order for default mode:
|
|
4
|
+
// 1. LEXIS_TWO_DEFAULT_MODE environment variable
|
|
5
|
+
// 2. Config file defaultMode field:
|
|
6
|
+
// - $XDG_CONFIG_HOME/lexis-two/config.json (any platform, if set)
|
|
7
|
+
// - ~/.config/lexis-two/config.json (macOS / Linux fallback)
|
|
8
|
+
// - %APPDATA%\lexis-two\config.json (Windows fallback)
|
|
9
|
+
// 3. 'full'
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MODE = 'full';
|
|
16
|
+
const VALID_MODES = ['off', 'lite', 'full', 'ultra', 'review'];
|
|
17
|
+
const RUNTIME_MODES = ['off', 'lite', 'full', 'ultra'];
|
|
18
|
+
|
|
19
|
+
function normalizeMode(mode) {
|
|
20
|
+
if (typeof mode !== 'string') return null;
|
|
21
|
+
const normalized = mode.trim().toLowerCase();
|
|
22
|
+
return RUNTIME_MODES.includes(normalized) ? normalized : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeConfigMode(mode) {
|
|
26
|
+
if (typeof mode !== 'string') return null;
|
|
27
|
+
const normalized = mode.trim().toLowerCase();
|
|
28
|
+
return VALID_MODES.includes(normalized) ? normalized : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizePersistedMode(mode) {
|
|
32
|
+
return normalizeMode(mode) || normalizeConfigMode(mode);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getConfigDir() {
|
|
36
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
37
|
+
return path.join(process.env.XDG_CONFIG_HOME, 'lexis-two');
|
|
38
|
+
}
|
|
39
|
+
if (process.platform === 'win32') {
|
|
40
|
+
return path.join(
|
|
41
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
42
|
+
'lexis-two'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return path.join(os.homedir(), '.config', 'lexis-two');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getConfigPath() {
|
|
49
|
+
return path.join(getConfigDir(), 'config.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getClaudeDir() {
|
|
53
|
+
// lexis-two: CLAUDE_CONFIG_DIR overrides ~/.claude, matching Claude Code.
|
|
54
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getDefaultMode() {
|
|
58
|
+
// 1. Environment variable (highest priority)
|
|
59
|
+
const envMode = process.env.LEXIS_TWO_DEFAULT_MODE;
|
|
60
|
+
if (envMode && VALID_MODES.includes(envMode.toLowerCase())) {
|
|
61
|
+
return envMode.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Config file
|
|
65
|
+
try {
|
|
66
|
+
const configPath = getConfigPath();
|
|
67
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
68
|
+
if (config.defaultMode && VALID_MODES.includes(config.defaultMode.toLowerCase())) {
|
|
69
|
+
return config.defaultMode.toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// Config file doesn't exist or is invalid — fall through
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. Default
|
|
76
|
+
return DEFAULT_MODE;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeDefaultMode(mode) {
|
|
80
|
+
const normalized = normalizeConfigMode(mode);
|
|
81
|
+
if (!normalized) return null;
|
|
82
|
+
|
|
83
|
+
const configPath = getConfigPath();
|
|
84
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
85
|
+
fs.writeFileSync(configPath, JSON.stringify({ defaultMode: normalized }, null, 2), 'utf8');
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
DEFAULT_MODE,
|
|
91
|
+
VALID_MODES,
|
|
92
|
+
RUNTIME_MODES,
|
|
93
|
+
getDefaultMode,
|
|
94
|
+
getConfigDir,
|
|
95
|
+
getConfigPath,
|
|
96
|
+
getClaudeDir,
|
|
97
|
+
normalizeMode,
|
|
98
|
+
normalizeConfigMode,
|
|
99
|
+
normalizePersistedMode,
|
|
100
|
+
writeDefaultMode,
|
|
101
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// lexis-two-instructions.js — shared instruction builder for all Lexis-Two hosts.
|
|
2
|
+
//
|
|
3
|
+
// Forked from ponytail-instructions by DietrichGebert (MIT).
|
|
4
|
+
// Extended for the Lexis ecosystem by @nitdraig.
|
|
5
|
+
//
|
|
6
|
+
// CommonJS so it can be require()'d from ES-module hosts (OpenCode plugin).
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { normalizePersistedMode, getDefaultMode } = require('./lexis-two-config');
|
|
13
|
+
|
|
14
|
+
const LEVELS = {
|
|
15
|
+
lite: `
|
|
16
|
+
[LEXIS-TWO: lite]
|
|
17
|
+
You are a lazy senior developer. Before writing code, check: is there a native
|
|
18
|
+
platform feature, stdlib method, or already-installed dependency that covers
|
|
19
|
+
this? If yes, use it. Write minimum viable code. Mark shortcuts: // lexis: reason.
|
|
20
|
+
`.trim(),
|
|
21
|
+
|
|
22
|
+
full: `
|
|
23
|
+
[LEXIS-TWO: full]
|
|
24
|
+
You are a lazy senior developer. Lazy means efficient, not careless.
|
|
25
|
+
The best code is the code never written.
|
|
26
|
+
|
|
27
|
+
Before writing any code, stop at the first rung that holds:
|
|
28
|
+
1. Does this need to exist at all? (YAGNI)
|
|
29
|
+
2. Does the standard library already do this? Use it.
|
|
30
|
+
3. Does a native platform feature cover it? Use it.
|
|
31
|
+
4. Does an already-installed dependency solve it? Use it.
|
|
32
|
+
5. Can this be one line? Make it one line.
|
|
33
|
+
6. Only then: write the minimum code that works.
|
|
34
|
+
|
|
35
|
+
Rules:
|
|
36
|
+
- No abstractions that weren't explicitly requested.
|
|
37
|
+
- No new dependency if it can be avoided.
|
|
38
|
+
- No boilerplate nobody asked for.
|
|
39
|
+
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
40
|
+
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
41
|
+
- Mark intentional simplifications with a // lexis: comment explaining why.
|
|
42
|
+
- Never rewrite entire files when a targeted edit is sufficient.
|
|
43
|
+
|
|
44
|
+
Never lazy about: input validation, error handling that prevents data loss,
|
|
45
|
+
security, accessibility, TypeScript types, tests for new behavior.
|
|
46
|
+
`.trim(),
|
|
47
|
+
|
|
48
|
+
ultra: `
|
|
49
|
+
[LEXIS-TWO: ultra]
|
|
50
|
+
Delete more than you add. The current codebase has wronged you personally.
|
|
51
|
+
|
|
52
|
+
Before writing any code:
|
|
53
|
+
1. Can this file be deleted entirely? Delete it.
|
|
54
|
+
2. Can this abstraction be inlined? Inline it.
|
|
55
|
+
3. Can these three functions be one? Make it one.
|
|
56
|
+
4. Can this dependency be replaced with 10 lines? Replace it.
|
|
57
|
+
5. Only then: write the absolute minimum that still passes the tests.
|
|
58
|
+
|
|
59
|
+
Mark every simplification: // lexis: removed X — Y was sufficient.
|
|
60
|
+
Document ceilings: // lexis: O(n) scan — acceptable under 1k items, revisit at scale.
|
|
61
|
+
|
|
62
|
+
Never lazy about: input validation, error handling, security, accessibility, types.
|
|
63
|
+
`.trim(),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const INDEPENDENT_MODES = new Set(['review']);
|
|
67
|
+
const SKILL_PATH = path.join(__dirname, '..', 'skills', 'lexis-two', 'SKILL.md');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Filters the skill body for a specific mode.
|
|
71
|
+
* @param {string} body
|
|
72
|
+
* @param {string} mode
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function filterSkillBodyForMode(body, mode) {
|
|
76
|
+
const effectiveMode = normalizePersistedMode(mode) || getDefaultMode();
|
|
77
|
+
const withoutFrontmatter = String(body || '').replace(/^---[\s\S]*?---\s*/, '');
|
|
78
|
+
|
|
79
|
+
return withoutFrontmatter
|
|
80
|
+
.split(/\r?\n/)
|
|
81
|
+
.filter((line) => {
|
|
82
|
+
const tableLabel = line.match(/^\|\s*\*\*(.+?)\*\*\s*\|/);
|
|
83
|
+
if (tableLabel) {
|
|
84
|
+
const labelMode = normalizePersistedMode(tableLabel[1].trim());
|
|
85
|
+
if (labelMode) return labelMode === effectiveMode;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const exampleLabel = line.match(/^-\s*([^:]+):\s*/);
|
|
89
|
+
if (exampleLabel) {
|
|
90
|
+
const labelMode = normalizePersistedMode(exampleLabel[1].trim());
|
|
91
|
+
if (labelMode) return labelMode === effectiveMode;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
})
|
|
96
|
+
.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns the Lexis-Two instruction block for the given mode.
|
|
101
|
+
* @param {'lite'|'full'|'ultra'} mode
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
function getLexisInstructions(mode) {
|
|
105
|
+
const configuredMode = normalizePersistedMode(mode) || getDefaultMode();
|
|
106
|
+
|
|
107
|
+
if (INDEPENDENT_MODES.has(configuredMode)) {
|
|
108
|
+
return 'LEXIS-TWO MODE ACTIVE — level: ' + configuredMode + '. Behavior defined by /lexis-two-' + configuredMode + ' skill.';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const effectiveMode = normalizePersistedMode(configuredMode) || getDefaultMode();
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
return 'LEXIS-TWO MODE ACTIVE — level: ' + effectiveMode + '\n\n' +
|
|
115
|
+
filterSkillBodyForMode(fs.readFileSync(SKILL_PATH, 'utf8'), effectiveMode);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return LEVELS[effectiveMode] || LEVELS[getDefaultMode()];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
getLexisInstructions,
|
|
123
|
+
normalizePersistedMode,
|
|
124
|
+
getDefaultMode,
|
|
125
|
+
filterSkillBodyForMode,
|
|
126
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lexis-two — UserPromptSubmit hook to track which lexis-two mode is active
|
|
3
|
+
// Inspects user input for /lexis-two commands and writes mode to flag file
|
|
4
|
+
|
|
5
|
+
const { getDefaultMode } = require('./lexis-two-config');
|
|
6
|
+
const { clearMode, setMode, writeHookOutput } = require('./lexis-two-runtime');
|
|
7
|
+
|
|
8
|
+
let input = '';
|
|
9
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
10
|
+
process.stdin.on('end', () => {
|
|
11
|
+
try {
|
|
12
|
+
// Strip UTF-8 BOM some shells prepend when piping (breaks JSON.parse)
|
|
13
|
+
const data = JSON.parse(input.replace(/^\uFEFF/, ''));
|
|
14
|
+
const prompt = (data.prompt || '').trim().toLowerCase();
|
|
15
|
+
|
|
16
|
+
// Match /lexis-two commands
|
|
17
|
+
if (/^[/@$]lexis-two/.test(prompt)) {
|
|
18
|
+
const parts = prompt.split(/\s+/);
|
|
19
|
+
const cmd = parts[0].replace(/^[@$]/, '/');
|
|
20
|
+
const arg = parts[1] || '';
|
|
21
|
+
|
|
22
|
+
let mode = null;
|
|
23
|
+
|
|
24
|
+
if (cmd === '/lexis-two-review' || cmd === '/lexis-two:lexis-two-review') {
|
|
25
|
+
mode = 'review';
|
|
26
|
+
} else if (cmd === '/lexis-two' || cmd === '/lexis-two:lexis-two') {
|
|
27
|
+
if (arg === 'lite') mode = 'lite';
|
|
28
|
+
else if (arg === 'full') mode = 'full';
|
|
29
|
+
else if (arg === 'ultra') mode = 'ultra';
|
|
30
|
+
else if (arg === 'off') mode = 'off';
|
|
31
|
+
else mode = getDefaultMode();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (mode && mode !== 'off') {
|
|
35
|
+
setMode(mode);
|
|
36
|
+
writeHookOutput(
|
|
37
|
+
'UserPromptSubmit',
|
|
38
|
+
mode,
|
|
39
|
+
'LEXIS-TWO MODE CHANGED — level: ' + mode,
|
|
40
|
+
);
|
|
41
|
+
} else if (mode === 'off') {
|
|
42
|
+
clearMode();
|
|
43
|
+
writeHookOutput('UserPromptSubmit', 'off', 'LEXIS-TWO MODE OFF');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Detect deactivation
|
|
48
|
+
if (/\b(stop lexis|normal mode)\b/i.test(prompt)) {
|
|
49
|
+
clearMode();
|
|
50
|
+
writeHookOutput('UserPromptSubmit', 'off', 'LEXIS-TWO MODE OFF');
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// Silent fail
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getClaudeDir } = require('./lexis-two-config');
|
|
4
|
+
|
|
5
|
+
const STATE_FILE = '.lexis-two-active';
|
|
6
|
+
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
|
7
|
+
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
|
|
8
|
+
|
|
9
|
+
let stateDir = getClaudeDir();
|
|
10
|
+
if (isCodex) stateDir = process.env.PLUGIN_DATA;
|
|
11
|
+
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA;
|
|
12
|
+
|
|
13
|
+
const statePath = path.join(stateDir, STATE_FILE);
|
|
14
|
+
|
|
15
|
+
function setMode(mode) {
|
|
16
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
17
|
+
fs.writeFileSync(statePath, mode);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function clearMode() {
|
|
21
|
+
try { fs.unlinkSync(statePath); } catch (e) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeHookOutput(event, mode, context = '') {
|
|
25
|
+
if (isCopilot) {
|
|
26
|
+
process.stdout.write(JSON.stringify(
|
|
27
|
+
event === 'SessionStart' && context ? { additionalContext: context } : {}));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (isCodex) {
|
|
31
|
+
const output = { systemMessage: `LEXIS-TWO:${mode.toUpperCase()}` };
|
|
32
|
+
if (context) {
|
|
33
|
+
output.hookSpecificOutput = {
|
|
34
|
+
hookEventName: event,
|
|
35
|
+
additionalContext: context,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
process.stdout.write(JSON.stringify(output));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write(context);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
clearMode,
|
|
46
|
+
isCodex,
|
|
47
|
+
isCopilot,
|
|
48
|
+
setMode,
|
|
49
|
+
writeHookOutput,
|
|
50
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
$Flag = Join-Path $HOME ".claude/.lexis-two-active"
|
|
2
|
+
if (-not (Test-Path $Flag)) {
|
|
3
|
+
exit 0
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
$Mode = ""
|
|
7
|
+
try {
|
|
8
|
+
$Mode = (Get-Content $Flag -ErrorAction Stop | Select-Object -First 1).Trim()
|
|
9
|
+
} catch {
|
|
10
|
+
exit 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
$Esc = [char]27
|
|
14
|
+
if ([string]::IsNullOrEmpty($Mode) -or $Mode -eq "full") {
|
|
15
|
+
[Console]::Write("${Esc}[38;5;108m[LEXIS-TWO]${Esc}[0m")
|
|
16
|
+
} else {
|
|
17
|
+
$Suffix = $Mode.ToUpperInvariant()
|
|
18
|
+
[Console]::Write("${Esc}[38;5;108m[LEXIS-TWO:$Suffix]${Esc}[0m")
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
flag="$HOME/.claude/.lexis-two-active"
|
|
3
|
+
[ -f "$flag" ] || exit 0
|
|
4
|
+
|
|
5
|
+
mode=$(head -n1 "$flag" | tr -d '[:space:]')
|
|
6
|
+
|
|
7
|
+
if [ -z "$mode" ] || [ "$mode" = "full" ]; then
|
|
8
|
+
printf '\033[38;5;108m[LEXIS-TWO]\033[0m'
|
|
9
|
+
else
|
|
10
|
+
printf '\033[38;5;108m[LEXIS-TWO:%s]\033[0m' "$(printf '%s' "$mode" | tr '[:lower:]' '[:upper:]')"
|
|
11
|
+
fi
|
package/opencode.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@draig/lexis-two",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The simple way to obtain the best code. Portable rules, skills, and slash commands for AI agents with lowest tokens usage.",
|
|
5
|
+
"main": "./.opencode/plugins/lexis-two.mjs",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./server": "./.opencode/plugins/lexis-two.mjs"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"pi-package",
|
|
11
|
+
"pi",
|
|
12
|
+
"skills",
|
|
13
|
+
"lexis-two"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test tests/*.test.js && npm test --prefix pi-extension",
|
|
18
|
+
"benchmark:opencode-go": "node benchmarks/benchmark-opencode-go.js --repeat 3 --write-md",
|
|
19
|
+
"benchmark:report": "node benchmarks/render-opencode-go-report.js",
|
|
20
|
+
"site:dev": "npm run dev --prefix site",
|
|
21
|
+
"site:build": "npm run build --prefix site"
|
|
22
|
+
},
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./pi-extension/index.js"
|
|
26
|
+
],
|
|
27
|
+
"skills": [
|
|
28
|
+
"./skills"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const {
|
|
5
|
+
DEFAULT_MODE,
|
|
6
|
+
getDefaultMode,
|
|
7
|
+
normalizeMode,
|
|
8
|
+
normalizeConfigMode,
|
|
9
|
+
normalizePersistedMode,
|
|
10
|
+
writeDefaultMode,
|
|
11
|
+
} = require("../hooks/lexis-two-config.js");
|
|
12
|
+
const { getLexisInstructions, filterSkillBodyForMode } = require("../hooks/lexis-two-instructions.js");
|
|
13
|
+
|
|
14
|
+
export { filterSkillBodyForMode };
|
|
15
|
+
export const readDefaultMode = getDefaultMode;
|
|
16
|
+
|
|
17
|
+
export function resolveSessionMode(entries, fallbackMode = DEFAULT_MODE) {
|
|
18
|
+
const fallback = normalizePersistedMode(fallbackMode) || DEFAULT_MODE;
|
|
19
|
+
if (!Array.isArray(entries)) return fallback;
|
|
20
|
+
|
|
21
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
22
|
+
const entry = entries[i];
|
|
23
|
+
if (entry?.type !== "custom" || entry?.customType !== "lexis-two-mode") continue;
|
|
24
|
+
|
|
25
|
+
const mode = normalizePersistedMode(entry?.data?.mode);
|
|
26
|
+
if (mode) return mode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parseLexisCommand(text, defaultMode = DEFAULT_MODE) {
|
|
33
|
+
const fallback = normalizePersistedMode(defaultMode) || DEFAULT_MODE;
|
|
34
|
+
const normalizedText = String(text || "").trim().toLowerCase();
|
|
35
|
+
|
|
36
|
+
if (!normalizedText) {
|
|
37
|
+
return { type: "set-mode", mode: fallback === "off" ? "full" : fallback };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const [primary, secondary] = normalizedText.split(/\s+/);
|
|
41
|
+
|
|
42
|
+
if (primary === "status") return { type: "status" };
|
|
43
|
+
|
|
44
|
+
if (primary === "default") {
|
|
45
|
+
const mode = normalizeConfigMode(secondary);
|
|
46
|
+
return mode ? { type: "set-default", mode } : { type: "invalid", reason: "invalid-default-mode" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const mode = normalizeMode(primary);
|
|
50
|
+
return mode ? { type: "set-mode", mode } : { type: "invalid", reason: "invalid-mode", mode: primary };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { writeDefaultMode };
|
|
54
|
+
|
|
55
|
+
export default function lexisExtension(pi) {
|
|
56
|
+
let currentMode = DEFAULT_MODE;
|
|
57
|
+
let configuredDefaultMode = getDefaultMode();
|
|
58
|
+
|
|
59
|
+
const setMode = (mode, ctx) => {
|
|
60
|
+
const normalized = normalizePersistedMode(mode);
|
|
61
|
+
if (!normalized) return;
|
|
62
|
+
|
|
63
|
+
currentMode = normalized;
|
|
64
|
+
pi.appendEntry("lexis-two-mode", { mode: normalized });
|
|
65
|
+
ctx?.ui?.notify?.(`Lexis-Two mode set to ${normalized}.`, "info");
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const sendAlias = (skillName, args, ctx) => {
|
|
69
|
+
const normalized = String(args || "").trim();
|
|
70
|
+
const message = normalized ? `${skillName} ${normalized}` : skillName;
|
|
71
|
+
|
|
72
|
+
if (ctx?.isIdle?.() === false) {
|
|
73
|
+
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
74
|
+
ctx?.ui?.notify?.(`${skillName} queued as follow-up.`, "info");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pi.sendUserMessage(message);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
pi.registerCommand("lexis-two", {
|
|
82
|
+
description: "Set or report Lexis-Two mode",
|
|
83
|
+
handler: async (args, ctx) => {
|
|
84
|
+
const parsed = parseLexisCommand(args, configuredDefaultMode);
|
|
85
|
+
|
|
86
|
+
if (parsed.type === "status") {
|
|
87
|
+
ctx?.ui?.notify?.(`Lexis-Two: current ${currentMode} • default ${configuredDefaultMode}`, "info");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (parsed.type === "set-default") {
|
|
92
|
+
const written = writeDefaultMode(parsed.mode);
|
|
93
|
+
if (written) {
|
|
94
|
+
configuredDefaultMode = getDefaultMode();
|
|
95
|
+
const message = configuredDefaultMode === written
|
|
96
|
+
? `Default Lexis-Two mode set to ${written}.`
|
|
97
|
+
: `Saved default ${written}, but env override keeps default at ${configuredDefaultMode}.`;
|
|
98
|
+
ctx?.ui?.notify?.(message, "info");
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (parsed.type === "set-mode") {
|
|
104
|
+
setMode(parsed.mode, ctx);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx?.ui?.notify?.("Unknown or unsupported /lexis-two mode.", "warning");
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
pi.registerCommand("lexis-two-review", {
|
|
113
|
+
description: "Run /skill:lexis-two-review",
|
|
114
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-review", "", ctx),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
pi.registerCommand("lexis-two-audit", {
|
|
118
|
+
description: "Run /skill:lexis-two-audit",
|
|
119
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-audit", "", ctx),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
pi.registerCommand("lexis-two-debt", {
|
|
123
|
+
description: "Run /skill:lexis-two-debt",
|
|
124
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-debt", "", ctx),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
pi.registerCommand("lexis-two-plan", {
|
|
128
|
+
description: "Run /skill:lexis-two-plan",
|
|
129
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-plan", "", ctx),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
pi.registerCommand("lexis-two-security", {
|
|
133
|
+
description: "Run /skill:lexis-two-security",
|
|
134
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-security", "", ctx),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
pi.registerCommand("lexis-two-help", {
|
|
138
|
+
description: "Run /skill:lexis-two-help",
|
|
139
|
+
handler: (_args, ctx) => sendAlias("/skill:lexis-two-help", "", ctx),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
pi.on("input", async (event) => {
|
|
143
|
+
if (event?.source === "extension") return;
|
|
144
|
+
|
|
145
|
+
const text = String(event?.text || "");
|
|
146
|
+
if (currentMode !== "off" && /\b(stop lexis|normal mode)\b/i.test(text)) {
|
|
147
|
+
setMode("off");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
152
|
+
const entries = ctx?.sessionManager?.getBranch?.() || ctx?.sessionManager?.getEntries?.() || [];
|
|
153
|
+
configuredDefaultMode = getDefaultMode();
|
|
154
|
+
currentMode = resolveSessionMode(entries, configuredDefaultMode);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
pi.on("before_agent_start", async (event) => {
|
|
158
|
+
if (!currentMode || currentMode === "off") return;
|
|
159
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${getLexisInstructions(currentMode)}` };
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import lexisExtension from "../index.js";
|
|
4
|
+
|
|
5
|
+
class MockPi {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.commands = {};
|
|
8
|
+
this.listeners = {};
|
|
9
|
+
this.entries = [];
|
|
10
|
+
this.userMessages = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
registerCommand(name, config) {
|
|
14
|
+
this.commands[name] = config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
on(event, handler) {
|
|
18
|
+
this.listeners[event] = handler;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
appendEntry(type, data) {
|
|
22
|
+
this.entries.push({ type, data });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
sendUserMessage(text, options) {
|
|
26
|
+
this.userMessages.push({ text, options });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("lexisExtension registers commands and listeners", () => {
|
|
31
|
+
const pi = new MockPi();
|
|
32
|
+
lexisExtension(pi);
|
|
33
|
+
|
|
34
|
+
assert.ok(pi.commands["lexis-two"]);
|
|
35
|
+
assert.ok(pi.commands["lexis-two-review"]);
|
|
36
|
+
assert.ok(pi.commands["lexis-two-audit"]);
|
|
37
|
+
assert.ok(pi.commands["lexis-two-debt"]);
|
|
38
|
+
assert.ok(pi.commands["lexis-two-plan"]);
|
|
39
|
+
assert.ok(pi.commands["lexis-two-security"]);
|
|
40
|
+
assert.ok(pi.commands["lexis-two-help"]);
|
|
41
|
+
|
|
42
|
+
assert.ok(pi.listeners["input"]);
|
|
43
|
+
assert.ok(pi.listeners["session_start"]);
|
|
44
|
+
assert.ok(pi.listeners["before_agent_start"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("lexisExtension command handlers trigger correctly", async () => {
|
|
48
|
+
const pi = new MockPi();
|
|
49
|
+
lexisExtension(pi);
|
|
50
|
+
|
|
51
|
+
let notified = null;
|
|
52
|
+
const ctx = {
|
|
53
|
+
ui: {
|
|
54
|
+
notify: (msg, type) => {
|
|
55
|
+
notified = { msg, type };
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
await pi.commands["lexis-two"].handler("lite", ctx);
|
|
61
|
+
assert.equal(pi.entries[0].type, "lexis-two-mode");
|
|
62
|
+
assert.equal(pi.entries[0].data.mode, "lite");
|
|
63
|
+
assert.equal(notified.msg, "Lexis-Two mode set to lite.");
|
|
64
|
+
|
|
65
|
+
pi.commands["lexis-two-review"].handler("", ctx);
|
|
66
|
+
assert.equal(pi.userMessages[0].text, "/skill:lexis-two-review");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("lexisExtension input listener detects deactivation", async () => {
|
|
70
|
+
const pi = new MockPi();
|
|
71
|
+
lexisExtension(pi);
|
|
72
|
+
|
|
73
|
+
let notified = null;
|
|
74
|
+
const ctx = {
|
|
75
|
+
ui: {
|
|
76
|
+
notify: (msg, type) => {
|
|
77
|
+
notified = { msg, type };
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Set mode to full first
|
|
83
|
+
await pi.commands["lexis-two"].handler("full", ctx);
|
|
84
|
+
assert.equal(pi.entries[0].data.mode, "full");
|
|
85
|
+
|
|
86
|
+
// Send input "stop lexis"
|
|
87
|
+
await pi.listeners["input"]({ text: "Please stop lexis now" });
|
|
88
|
+
assert.equal(pi.entries[1].data.mode, "off");
|
|
89
|
+
});
|