boxbox-plugin 0.0.0-stage → 0.2.7
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/LICENSE +21 -0
- package/README.md +178 -2
- package/bin/boxbox.mjs +121 -0
- package/package.json +17 -5
- package/payload/bundle.json +8 -0
- package/payload/claude/.claude-plugin/marketplace.json +15 -0
- package/payload/claude/bundle.json +9 -0
- package/payload/claude/claude-registration.mjs +80 -0
- package/payload/claude/codex-registration.mjs +62 -0
- package/payload/claude/install-state.mjs +46 -0
- package/payload/claude/install.mjs +150 -0
- package/payload/claude/packages/boxbox-0.2.7.tgz +0 -0
- package/payload/claude/platform.mjs +106 -0
- package/payload/claude/plugin/.claude-plugin/plugin.json +18 -0
- package/payload/claude/plugin/agents/readability-inspector.md +23 -0
- package/payload/claude/plugin/hooks/hooks.json +54 -0
- package/payload/claude/plugin/scripts/boxbox.mjs +42 -0
- package/payload/claude/plugin/skills/boxbox/SKILL.md +188 -0
- package/payload/claude/plugin/skills/boxbox/references/charter.md +72 -0
- package/payload/claude/plugin/skills/boxbox/references/grammar.md +147 -0
- package/payload/claude/plugin/skills/boxbox/references/readability-contract.md +311 -0
- package/payload/claude/plugin/skills/boxbox/references/typescript-grid-template.md +334 -0
- package/payload/claude-registration.mjs +80 -0
- package/payload/codex/.agents/plugins/marketplace.json +12 -0
- package/payload/codex/bundle.json +9 -0
- package/payload/codex/claude-registration.mjs +80 -0
- package/payload/codex/codex-registration.mjs +62 -0
- package/payload/codex/install-state.mjs +46 -0
- package/payload/codex/install.mjs +150 -0
- package/payload/codex/packages/boxbox-0.2.7.tgz +0 -0
- package/payload/codex/platform.mjs +106 -0
- package/payload/codex/plugin/.codex-plugin/plugin.json +29 -0
- package/payload/codex/plugin/hooks/hooks.json +42 -0
- package/payload/codex/plugin/scripts/boxbox.mjs +42 -0
- package/payload/codex/plugin/skills/boxbox/SKILL.md +188 -0
- package/payload/codex/plugin/skills/boxbox/references/charter.md +72 -0
- package/payload/codex/plugin/skills/boxbox/references/grammar.md +147 -0
- package/payload/codex/plugin/skills/boxbox/references/readability-contract.md +311 -0
- package/payload/codex/plugin/skills/boxbox/references/typescript-grid-template.md +334 -0
- package/payload/codex-registration.mjs +62 -0
- package/payload/install-state.mjs +46 -0
- package/payload/platform.mjs +106 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process" ;
|
|
2
|
+
import { createHash } from "node:crypto" ;
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs" ;
|
|
4
|
+
import { join, resolve } from "node:path" ;
|
|
5
|
+
import { fileURLToPath } from "node:url" ;
|
|
6
|
+
import {
|
|
7
|
+
configureSkill ,
|
|
8
|
+
engineCommand ,
|
|
9
|
+
findCommand ,
|
|
10
|
+
hookCommands ,
|
|
11
|
+
pathLaunchers ,
|
|
12
|
+
supportedNode ,
|
|
13
|
+
} from "./platform.mjs" ;
|
|
14
|
+
|
|
15
|
+
import { installationState } from "./install-state.mjs" ;
|
|
16
|
+
import { claudeRegistration } from "./claude-registration.mjs" ;
|
|
17
|
+
import { codexRegistration } from "./codex-registration.mjs" ;
|
|
18
|
+
|
|
19
|
+
const root = fileURLToPath(new URL("./", import.meta.url)) ;
|
|
20
|
+
const windows = process.platform === "win32" ;
|
|
21
|
+
const engineOnly = process.argv.length === 3 && process.argv[2] === "--engine-only" ;
|
|
22
|
+
|
|
23
|
+
function fail(message) {
|
|
24
|
+
console.error(`[BoxBox] ${message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function run(executable, args, options = {}) {
|
|
29
|
+
const child = spawnSync(executable, args, { stdio: "inherit", ...options });
|
|
30
|
+
if (child.error) fail(`Cannot run ${executable}: ${child.error.message}`);
|
|
31
|
+
if (child.signal) fail(`${executable} terminated by ${child.signal}`);
|
|
32
|
+
if (child.status !== 0) {
|
|
33
|
+
const details = String(child.stderr || child.stdout || "").trim();
|
|
34
|
+
console.error(`[BoxBox] ${executable} ${args.join(" ")} failed${details ? `:\n${details}` : "."}`);
|
|
35
|
+
process.exit(child.status ?? 1);
|
|
36
|
+
}
|
|
37
|
+
return child;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function command(name) {
|
|
41
|
+
const resolved = findCommand(name);
|
|
42
|
+
if (resolved) return resolved;
|
|
43
|
+
fail(`${name} was not found on PATH. Install ${name === "npm" ? "Node.js with npm" : "the selected host CLI with plugin support"}, open a new terminal, then rerun the BoxBox installer.${windows ? " Use the standard npm installation or a native host .exe." : ""}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function invoke(tool, args, options) {
|
|
47
|
+
return run(tool.executable, [...tool.args, ...args], options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function bundledEngine(tarball, metadata) {
|
|
51
|
+
if (typeof metadata.engineSha256 !== "string" || !/^[0-9a-f]{64}$/u.test(metadata.engineSha256)) fail("Bundled engine integrity metadata is missing or invalid. Reinstall this BoxBox installer package, then rerun it.");
|
|
52
|
+
if (!existsSync(tarball)) fail(`Bundled engine is missing: ${tarball}. Reinstall this BoxBox installer package, then rerun it.`);
|
|
53
|
+
const actual = createHash("sha256").update(readFileSync(tarball)).digest("hex");
|
|
54
|
+
if (actual !== metadata.engineSha256) fail("Bundled engine integrity check failed. The bundled engine bytes do not belong to this installer. Reinstall this BoxBox installer package, then rerun it.");
|
|
55
|
+
return tarball;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (process.argv.length > 2 && !engineOnly) fail("Usage: node install.mjs [--engine-only]");
|
|
59
|
+
if (!supportedNode(process.versions.node)) fail(`Node.js >=22.22.3 is required; found ${process.versions.node}. Install a supported Node.js release and rerun the BoxBox installer.`);
|
|
60
|
+
|
|
61
|
+
const metadata = JSON.parse(readFileSync(join(root, "bundle.json"), "utf8"));
|
|
62
|
+
if (!["codex", "claude"].includes(metadata.host)) fail("The installed payload has an invalid host. Rerun the BoxBox installer.");
|
|
63
|
+
const state = installationState(root, metadata);
|
|
64
|
+
if (state.read()?.phase !== "prepared") fail("A fresh parent installation receipt is required. Rerun the BoxBox installer.");
|
|
65
|
+
const tarball = bundledEngine(join(root, "packages", `${metadata.name}-${metadata.version}.tgz`), metadata) ;
|
|
66
|
+
const npm = command ("npm" ) ;
|
|
67
|
+
const quiet = { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] } ;
|
|
68
|
+
invoke(npm, ["--version"], quiet);
|
|
69
|
+
const host = engineOnly ? undefined : command(metadata.host);
|
|
70
|
+
if (host) {
|
|
71
|
+
invoke(host, ["--version"], quiet);
|
|
72
|
+
invoke(host, ["plugin", metadata.host === "codex" ? "add" : "install", "--help"], quiet);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const claude = host && metadata.host === "claude" ? claudeRegistration(root, metadata, (args, options) => invoke(host, args, options)) : undefined;
|
|
76
|
+
const codex = host && metadata.host === "codex" ? codexRegistration(root, metadata, (args, options = {}) => {
|
|
77
|
+
const child = spawnSync(host.executable, [...host.args, ...args], { stdio: "inherit", ...options });
|
|
78
|
+
if (child.error) throw child.error;
|
|
79
|
+
if (child.signal || child.status !== 0) {
|
|
80
|
+
const details = String(child.stderr || child.stdout || "").trim();
|
|
81
|
+
throw new Error(`Codex ${args.join(" ")} failed${details ? `: ${details}` : ""}`);
|
|
82
|
+
}
|
|
83
|
+
return child;
|
|
84
|
+
}) : undefined;
|
|
85
|
+
try {
|
|
86
|
+
claude?.preflight();
|
|
87
|
+
codex?.preflight();
|
|
88
|
+
} catch (error) {
|
|
89
|
+
fail(error.message);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const runtimeDirectory = join(root, "runtime");
|
|
93
|
+
mkdirSync(runtimeDirectory, { recursive: true });
|
|
94
|
+
writeFileSync(join(runtimeDirectory, "package.json"), JSON.stringify({ private: true, type: "module", name: `boxbox-${metadata.host}-runtime` }, null, "\t") + "\n");
|
|
95
|
+
invoke(npm, ["install", "--ignore-scripts", "--no-audit", "--no-fund", "--save-exact", tarball], { cwd: runtimeDirectory });
|
|
96
|
+
|
|
97
|
+
const node = realpathSync(process.execPath ) ;
|
|
98
|
+
const cli = resolve (runtimeDirectory, "node_modules", metadata.name, "dist/cli.js") ;
|
|
99
|
+
if (!existsSync(cli)) fail("The local engine did not install correctly. Keep the npm error output and rerun the BoxBox installer.");
|
|
100
|
+
const version = run(node, [cli, "--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] }).stdout.trim();
|
|
101
|
+
if (version !== metadata.version) fail(`Installed engine version mismatch: ${version}`);
|
|
102
|
+
|
|
103
|
+
const plugin = join(root, "plugin") ;
|
|
104
|
+
const launcher = join(plugin, "scripts/boxbox.mjs") ;
|
|
105
|
+
const configuration = join(plugin, "runtime.json") ;
|
|
106
|
+
const previous = existsSync(configuration) ? JSON.parse(readFileSync(configuration, "utf8")) : {} ;
|
|
107
|
+
const skillCommand = engineCommand(node, launcher, windows, metadata.host) ;
|
|
108
|
+
const documents = [join(plugin, "skills", metadata.name, "SKILL.md"), ...(metadata.host === "claude" ? [join(plugin, "agents/readability-inspector.md")] : [])] ;
|
|
109
|
+
for (const path of documents) {
|
|
110
|
+
try {
|
|
111
|
+
writeFileSync(path, configureSkill(readFileSync(path, "utf8"), skillCommand, previous.skillCommand, windows));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
fail(error.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
writeFileSync(configuration, JSON.stringify({ node, cli, skillCommand }, null, "\t") + "\n");
|
|
117
|
+
mkdirSync(join(plugin, "bin"), { recursive: true });
|
|
118
|
+
for (const [path, text] of Object.entries(pathLaunchers(node, launcher))) writeFileSync(join(plugin, path), text, { mode: 0o755 });
|
|
119
|
+
|
|
120
|
+
const hooksPath = join (plugin, "hooks/hooks.json" ) ;
|
|
121
|
+
const hooks = JSON.parse(readFileSync(hooksPath, "utf8")) ;
|
|
122
|
+
for (const [event, mode] of [["SessionStart", "session"], ["PostToolUse", "post"], ["Stop", "stop"]]) {
|
|
123
|
+
for (const group of hooks.hooks[event]) {
|
|
124
|
+
for (const hook of group.hooks) {
|
|
125
|
+
delete hook.commandWindows;
|
|
126
|
+
delete hook.args;
|
|
127
|
+
Object.assign(hook, hookCommands(node, mode, metadata.host));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
writeFileSync(hooksPath, JSON.stringify(hooks, null, "\t") + "\n");
|
|
132
|
+
|
|
133
|
+
if (host) {
|
|
134
|
+
state.advance("host-registration-started");
|
|
135
|
+
try {
|
|
136
|
+
if (codex) codex.transition();
|
|
137
|
+
else {
|
|
138
|
+
invoke(host, ["plugin", "marketplace", "add", root]);
|
|
139
|
+
claude?.marketplace();
|
|
140
|
+
invoke(host, ["plugin", "install", `${metadata.name}@${metadata.marketplace}`, "--scope", "user"]);
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
fail(error.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
claude?.verify();
|
|
147
|
+
state.advance("verified");
|
|
148
|
+
console.log(engineOnly ? "BoxBox local engine is ready; host checks and registration were skipped." : `BoxBox is installed for ${metadata.host === "codex" ? "Codex" : "Claude Code"}. Restart that host and select the BoxBox skill. Review the current hooks manually before relying on them.`);
|
|
149
|
+
console.log(`Permanent installation: ${root}`);
|
|
150
|
+
console.log("Approved TypeScript call and value declarations and Python simple assignments can format without an external formatter. Run check and guide, then format --dry-run before an explicit format request; other patterns require approval.");
|
|
Binary file
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process" ;
|
|
2
|
+
import { existsSync } from "node:fs" ;
|
|
3
|
+
import { homedir } from "node:os" ;
|
|
4
|
+
import { posix, win32 } from "node:path" ;
|
|
5
|
+
|
|
6
|
+
export function supportedNode(version) {
|
|
7
|
+
const [major, minor, patch] = version.split(".").map(Number);
|
|
8
|
+
return major > 22 || (major === 22 && (minor > 22 || (minor === 22 && patch >= 3)));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function installationRoot(version, host, { windows = process.platform === "win32", environment = process.env, home = homedir() } = {}) {
|
|
12
|
+
if (!["codex", "claude"].includes(host)) throw new Error("Expected codex or claude installation host.");
|
|
13
|
+
if (windows) {
|
|
14
|
+
if (!environment.LOCALAPPDATA || !win32.isAbsolute(environment.LOCALAPPDATA)) throw new Error("LOCALAPPDATA must point to an absolute user data directory on Windows.");
|
|
15
|
+
return win32.join(environment.LOCALAPPDATA, "BoxBox", host === "codex" ? "Codex" : "Claude", version);
|
|
16
|
+
}
|
|
17
|
+
const data = environment.XDG_DATA_HOME || posix.join(home, ".local/share");
|
|
18
|
+
if (!posix.isAbsolute(data)) throw new Error("XDG_DATA_HOME must be an absolute directory when set.");
|
|
19
|
+
return posix.join(data, "boxbox", host, version);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function identifiesCodex(executable, probe) {
|
|
23
|
+
try {
|
|
24
|
+
const result = probe(executable, ["--version"], {
|
|
25
|
+
encoding: "utf8", shell: false, windowsHide: true,
|
|
26
|
+
timeout: 3_000, maxBuffer: 4_096, killSignal: "SIGKILL", stdio: ["ignore", "pipe", "pipe"],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return result.status === 0 && !result.error && !result.signal
|
|
30
|
+
&& /^codex-cli \d+\.\d+\.\d+(?:-[0-9a-z.-]+)?(?:\+[0-9a-z.-]+)?$/iu.test(String(result.stdout ?? "").trim());
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Resolve standard Windows npm shims without a shell, and skip unrelated native programs named codex.exe. */
|
|
37
|
+
export function findCommand(name, { windows = process.platform === "win32", node = process.execPath, path = process.env.PATH || "", exists = existsSync, probe = spawnSync } = {}) {
|
|
38
|
+
const paths = windows ? win32 : posix;
|
|
39
|
+
const directories = [...path.split(windows ? ";" : ":").filter(Boolean), paths.dirname(node)];
|
|
40
|
+
for (const directory of directories) {
|
|
41
|
+
if (windows) {
|
|
42
|
+
if (name === "npm" || name === "codex") {
|
|
43
|
+
const script = paths.join(directory, name === "npm" ? "node_modules/npm/bin/npm-cli.js" : "node_modules/@openai/codex/bin/codex.js");
|
|
44
|
+
if (exists(script)) return { executable: node, args: [script] };
|
|
45
|
+
}
|
|
46
|
+
if (name === "claude") {
|
|
47
|
+
const packaged = paths.join(directory, "node_modules/@anthropic-ai/claude-code/bin/claude.exe");
|
|
48
|
+
if (exists(packaged)) return { executable: packaged, args: [] };
|
|
49
|
+
}
|
|
50
|
+
const native = paths.join(directory, `${name}.exe`);
|
|
51
|
+
if (name !== "npm" && exists(native) && (name !== "codex" || identifiesCodex(native, probe))) return { executable: native, args: [] };
|
|
52
|
+
} else {
|
|
53
|
+
const executable = paths.join(directory, name);
|
|
54
|
+
if (exists(executable)) return { executable, args: [] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function shellQuote(value) {
|
|
61
|
+
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function powershellQuote(value) {
|
|
65
|
+
return "'" + value.replaceAll(/['\u2018\u2019\u201A\u201B]/gu, quote => quote + quote) + "'";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function engineCommand(node, launcher, windows, host = "codex") {
|
|
69
|
+
if (windows && host === "claude") {
|
|
70
|
+
const payload = Buffer.from(JSON.stringify([node, launcher]), "utf8").toString("base64");
|
|
71
|
+
return `node -e "const [n,l]=JSON.parse(Buffer.from('${payload}','base64').toString('utf8'));const r=require('node:child_process').spawnSync(n,[l,...process.argv.slice(1)],{stdio:'inherit'});if(r.error){console.error(r.error.message);process.exit(1)}process.exit(r.status??1)" --`;
|
|
72
|
+
}
|
|
73
|
+
return windows ? `& ${powershellQuote(node)} ${powershellQuote(launcher)}` : `${shellQuote(node)} ${shellQuote(launcher)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `boxbox` and `update` on the host's PATH. Claude Code adds an enabled plugin's `bin/` folder to the PATH of its
|
|
78
|
+
* shell, so the user can type `! update boxbox` in the prompt. `update` answers only `update boxbox` and refuses any
|
|
79
|
+
* other target, so it never stands in for another tool's command. The sh forms serve Git Bash and POSIX shells; the
|
|
80
|
+
* .cmd forms serve cmd.exe and PowerShell on Windows.
|
|
81
|
+
*/
|
|
82
|
+
export function pathLaunchers(node, launcher) {
|
|
83
|
+
const posix = path => path.replaceAll("\\", "/") ;
|
|
84
|
+
const refusal = "update: only \\`update boxbox\\` is provided here (BoxBox plugin)." ;
|
|
85
|
+
return {
|
|
86
|
+
"bin/boxbox" : `#!/bin/sh\n# Generated by the BoxBox installer.\nexec ${shellQuote(posix(node))} ${shellQuote(posix(launcher))} "$@"\n`,
|
|
87
|
+
"bin/boxbox.cmd" : `@echo off\r\nrem Generated by the BoxBox installer.\r\n"${node}" "${launcher}" %*\r\n`,
|
|
88
|
+
"bin/update" : `#!/bin/sh\n# Generated by the BoxBox installer.\nif [ "\${1:-}" != boxbox ]; then printf '%s\\n' "${refusal}" >&2; exit 2; fi\nshift\nexec ${shellQuote(posix(node))} ${shellQuote(posix(launcher))} update "$@"\n`,
|
|
89
|
+
"bin/update.cmd" : `@echo off\r\nrem Generated by the BoxBox installer.\r\nif /i not "%~1"=="boxbox" (echo update: only "update boxbox" is provided here ^(BoxBox plugin^). 1>&2 & exit /b 2)\r\nshift\r\n"${node}" "${launcher}" update %1 %2 %3 %4 %5 %6 %7 %8 %9\r\n`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function configureSkill(skill, command, previousCommand, windows) {
|
|
94
|
+
const marker = "@BOXBOX_ENGINE_COMMAND@";
|
|
95
|
+
const oldCommand = skill.includes(marker) ? marker : previousCommand;
|
|
96
|
+
if (!oldCommand || !skill.includes(oldCommand)) throw new Error("The installed skill command is missing. Rerun the BoxBox installer to repair the plugin.");
|
|
97
|
+
return skill.replaceAll(oldCommand, () => command).replaceAll(windows ? "```bash" : "```powershell", windows ? "```powershell" : "```bash");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function hookCommands(node, mode, host = "codex") {
|
|
101
|
+
if (host === "claude") return { command: node, args: ["${CLAUDE_PLUGIN_ROOT}/scripts/boxbox.mjs", "hook", mode] };
|
|
102
|
+
return {
|
|
103
|
+
command: `${shellQuote(node)} "\${PLUGIN_ROOT}/scripts/boxbox.mjs" hook ${mode}`,
|
|
104
|
+
commandWindows: `& ${powershellQuote(node)} "$env:PLUGIN_ROOT/scripts/boxbox.mjs" hook ${mode}`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boxbox",
|
|
3
|
+
"version": "0.2.7",
|
|
4
|
+
"description": "Guides writing and refactoring TypeScript and Python, with syntax and tabular checks for TypeScript, Python, JSON and YAML and human-approved whitespace alignment.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Woo-JongHo"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"readability",
|
|
10
|
+
"typescript",
|
|
11
|
+
"python",
|
|
12
|
+
"code-style",
|
|
13
|
+
"align",
|
|
14
|
+
"json",
|
|
15
|
+
"yaml"
|
|
16
|
+
],
|
|
17
|
+
"skills": "./skills/",
|
|
18
|
+
"interface": {
|
|
19
|
+
"displayName": "BoxBox",
|
|
20
|
+
"shortDescription": "Write, refactor and align code for tabular readability",
|
|
21
|
+
"longDescription": "Guides agent-owned TypeScript and Python writing and refactoring; checks TypeScript, Python, JSON and YAML and aligns approved tables one file at a time. Unsupported structures remain unchanged. Structural changes require separate behavior verification.",
|
|
22
|
+
"developerName": "Woo-JongHo",
|
|
23
|
+
"category": "Coding",
|
|
24
|
+
"capabilities": [
|
|
25
|
+
"Read",
|
|
26
|
+
"Write"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "boxbox: inspects TypeScript, Python, JSON, and YAML session changes after matched file-edit and shell tools. Hooks are read-only by default; explicit align mode may apply approved alignment after tools. Stop never writes. Receipts stay in host plugin data; mode and update-cache state use user configuration. SessionStart may check GitHub tags once per day; BOXBOX_UPDATE_CHECK=0 disables that probe.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/boxbox.mjs\" hook session",
|
|
10
|
+
"timeout": 120,
|
|
11
|
+
"commandWindows": "node \"$env:PLUGIN_ROOT/scripts/boxbox.mjs\" hook session"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PostToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "^(Write|Edit|MultiEdit|apply_patch|Bash)$",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/boxbox.mjs\" hook post",
|
|
23
|
+
"timeout": 120,
|
|
24
|
+
"commandWindows": "node \"$env:PLUGIN_ROOT/scripts/boxbox.mjs\" hook post"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"Stop": [
|
|
30
|
+
{
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/boxbox.mjs\" hook stop",
|
|
35
|
+
"timeout": 120,
|
|
36
|
+
"commandWindows": "node \"$env:PLUGIN_ROOT/scripts/boxbox.mjs\" hook stop"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process" ;
|
|
2
|
+
import { existsSync, readFileSync, readSync } from "node:fs" ;
|
|
3
|
+
import { isAbsolute, resolve } from "node:path" ;
|
|
4
|
+
import { fileURLToPath } from "node:url" ;
|
|
5
|
+
|
|
6
|
+
const root = fileURLToPath(new URL("../", import.meta.url));
|
|
7
|
+
const configuration = resolve(root, "runtime.json");
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
if (!existsSync(configuration)) throw new Error("Local runtime is missing. Rerun the BoxBox installer before using this plugin.");
|
|
11
|
+
const runtime = JSON.parse(readFileSync(configuration, "utf8"));
|
|
12
|
+
for (const key of ["node", "cli"]) {
|
|
13
|
+
if (typeof runtime[key] !== "string" || !isAbsolute(runtime[key]) || !existsSync(runtime[key])) throw new Error(`Local runtime ${key} is missing. Rerun the BoxBox installer to repair the local runtime.`);
|
|
14
|
+
}
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
let input;
|
|
17
|
+
let data;
|
|
18
|
+
if (process.argv[2] === "hook") {
|
|
19
|
+
data = env.PLUGIN_DATA || env.CLAUDE_PLUGIN_DATA;
|
|
20
|
+
if (!data || !isAbsolute(data)) throw new Error("Hook requires an absolute PLUGIN_DATA or CLAUDE_PLUGIN_DATA directory from the host; no project files were written.");
|
|
21
|
+
env.CLAUDE_PLUGIN_DATA = data;
|
|
22
|
+
env.CLAUDE_PLUGIN_ROOT = env.PLUGIN_ROOT || env.CLAUDE_PLUGIN_ROOT || root;
|
|
23
|
+
const chunks = [];
|
|
24
|
+
const buffer = Buffer.alloc(65536);
|
|
25
|
+
let size = 0;
|
|
26
|
+
for (;;) {
|
|
27
|
+
const count = readSync(0, buffer, 0, buffer.length, null);
|
|
28
|
+
if (!count) break;
|
|
29
|
+
size += count;
|
|
30
|
+
if (size > 1024 * 1024) throw new Error("Hook input exceeds the 1 MiB limit.");
|
|
31
|
+
chunks.push(Buffer.from(buffer.subarray(0, count)));
|
|
32
|
+
}
|
|
33
|
+
input = Buffer.concat(chunks);
|
|
34
|
+
}
|
|
35
|
+
const child = spawnSync(runtime.node, [runtime.cli, ...process.argv.slice(2)], { stdio: input ? ["pipe", "inherit", "inherit"] : "inherit", input, env });
|
|
36
|
+
if (child.error) throw child.error;
|
|
37
|
+
if (child.signal) throw new Error(`Engine terminated by ${child.signal}`);
|
|
38
|
+
process.exitCode = child.status ?? 1;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`[BoxBox] ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: boxbox
|
|
3
|
+
description: Write or refactor TypeScript/Python, or align or check TypeScript, Python, JSON or YAML when the user requests BoxBox or spreadsheet-style readability, comparing same-role rows in minimum-width columns. Also handles on-demand BoxBox session usage reports.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BoxBox Code Readability
|
|
7
|
+
|
|
8
|
+
Let the reader follow execution top to bottom and compare same-role rows vertically. Rows share a comparison purpose;
|
|
9
|
+
columns are corresponding syntax boundaries, and cells hold their content. A comma in a nested call is not the same
|
|
10
|
+
axis as an outer argument separator. Use minimum-width columns without inventing work, wrappers or parameters to fill a grid.
|
|
11
|
+
|
|
12
|
+
Run the configured local launcher from the target repository root.
|
|
13
|
+
|
|
14
|
+
## Monitor request
|
|
15
|
+
|
|
16
|
+
When the user supplies `-monitor` (for example `/boxbox -monitor`), run the bundled launcher from the target repository:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
@BOXBOX_ENGINE_COMMAND@ monitor --project --host auto --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Complete this branch by reporting the returned HTML path, headline usage/change numbers and explicit unavailable or
|
|
23
|
+
estimated fields; keep the HTML body out of the conversation. The command always writes a self-contained host page
|
|
24
|
+
under the host's plugin data, including with `--json`. It requires absolute `CLAUDE_PLUGIN_DATA` or `PLUGIN_DATA`; if absent or relative,
|
|
25
|
+
relay the setup error and use the host launcher/environment rather than choosing an arbitrary directory. An optional
|
|
26
|
+
`--html <absolute-path>` replaces the default destination and must stay inside that same data folder. Use the user's explicit `--session`, `--host` or
|
|
27
|
+
`--no-paths` selection when supplied; `--session` replaces `--project`. Auto host selects Codex when a Codex
|
|
28
|
+
thread/session environment is present, otherwise Claude; choose explicitly for standalone sessions.
|
|
29
|
+
|
|
30
|
+
The report contains no transcript text, commands or source content, makes no model call and shows no dollar cost.
|
|
31
|
+
Old baselines can lack footprints, invalid fingerprints remain unknown and Codex exact step attribution is unavailable.
|
|
32
|
+
A monitor request is not an instruction to align or refactor source. Continue source work only when it is also requested.
|
|
33
|
+
|
|
34
|
+
## Choose the workflow
|
|
35
|
+
|
|
36
|
+
| Request | Workflow | Allowed change |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| Write new code using BoxBox readability | **Write** | The agent implements the requested behavior using applicable guidance. |
|
|
39
|
+
| Restructure existing code for BoxBox readability | **Refactor** | The agent improves structure within the authorized scope while preserving required behavior. |
|
|
40
|
+
| Align existing code without structural changes | **Align** | The engine changes approved horizontal whitespace only. |
|
|
41
|
+
| Inspect or review readability | **Check** | Report original-source syntax and readability without changing source. |
|
|
42
|
+
|
|
43
|
+
Write and Refactor are agent workflows, not CLI generation commands. Follow an explicit mode choice. For an ambiguous
|
|
44
|
+
"fix" request, check first and clarify when the choice between structural changes and alignment affects the result.
|
|
45
|
+
Preview is an execution option for every workflow: present a proposed diff without modifying the target file.
|
|
46
|
+
Author one file at a time within the user's authorized scope; continue across authorized files without requiring fresh feedback for each file.
|
|
47
|
+
For engine Check or Format, use repeated `--file` or `--directory` for an authorized multi-file selection; Align and Guide remain single-file commands.
|
|
48
|
+
|
|
49
|
+
## Running the engine
|
|
50
|
+
|
|
51
|
+
Every check and fix goes through this plugin's permanent local Node launcher, already configured by the installer. Run the commands below from the root of the target repository. No BOXBOX_CLI setup is needed. Never fetch a separate registry package for the engine. On Windows, run these commands through PowerShell.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
@BOXBOX_ENGINE_COMMAND@ doctor # runtime, parser, config and approval data status
|
|
55
|
+
@BOXBOX_ENGINE_COMMAND@ check --source --file <file> --json # source syntax/readability; no formatter or working copy
|
|
56
|
+
@BOXBOX_ENGINE_COMMAND@ guide --file <file> --json # approved rules and examples that apply to this file
|
|
57
|
+
@BOXBOX_ENGINE_COMMAND@ guide --file <file> --authoring --json
|
|
58
|
+
@BOXBOX_ENGINE_COMMAND@ align --file <file> --dry-run --json
|
|
59
|
+
@BOXBOX_ENGINE_COMMAND@ align --file <file> --json
|
|
60
|
+
@BOXBOX_ENGINE_COMMAND@ format --file <file> --dry-run --json
|
|
61
|
+
@BOXBOX_ENGINE_COMMAND@ format --file <file> --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Use `check --source` for readability preflight, selected Check workflows and checks after Write/Refactor/Align. It
|
|
65
|
+
reads source syntax, detected groups and approval facts without a formatter, formatting working copy or write attempt.
|
|
66
|
+
For an authorized formatter/editor/CI canonical check, use `check` without `--source`; the default still verifies the
|
|
67
|
+
configured formatter-then-BoxBox output and requires working-copy write permission.
|
|
68
|
+
- If `doctor`, `align` or `format` reports a formatter warning, relay it to the user with its conditions intact: a warning saying a formatter "may be in use" does not establish that it runs.
|
|
69
|
+
- The public commands support TypeScript, Python, strict JSON and YAML 1.2. Check and Format accept repeated `--file`
|
|
70
|
+
or bounded recursive `--directory`; each batch format is atomic per file and reports earlier writes if another file
|
|
71
|
+
fails. Align and Guide accept one file. Python inspection and formatting require a runnable `python3`.
|
|
72
|
+
- Structured Check accepts `.json`, `.yaml` and `.yml`. It considers only runs of at least three adjacent,
|
|
73
|
+
same-level, single-line scalar mapping entries for alignment. YAML multiline values, block scalars, anchors,
|
|
74
|
+
aliases, tags, complex keys, flow collections, sequences, directives and document markers are unsupported.
|
|
75
|
+
Report those rows and keep their bytes unchanged.
|
|
76
|
+
- Engine support is separate from write authorization. The recommended preset contains five approved
|
|
77
|
+
TypeScript/Python conventions. JSON/YAML Align must refuse until a complete applicable approval proof exists.
|
|
78
|
+
- `guide --authoring` reads approved guidance for the target language and scope, including a new or empty file.
|
|
79
|
+
Its examples guide composition; they do not prove that a proposed file is approved or safe to write.
|
|
80
|
+
- `align` runs without an external formatter and changes horizontal layout only. It preserves non-whitespace tokens,
|
|
81
|
+
comments, identifiers and order, leading indentation, physical lines and structural AST. If the engine needs wrapping,
|
|
82
|
+
operator movement or another structural change, report the refusal; stay within the selected mode.
|
|
83
|
+
- A newly detected shape or axis set has its own approval identity and suppresses an overlapping legacy group. Existing
|
|
84
|
+
approvals do not expand to it. A public whole-file write is denied when any candidate `PASS`/`FIXABLE` group lacks
|
|
85
|
+
approval, including an unchanged group. Per-group selective writes remain deferred.
|
|
86
|
+
- `format` retains the formatter pipeline for explicit requests to use it. If a formatter is configured for the language, it runs first.
|
|
87
|
+
Canonical Check and `format --dry-run` also execute a configured formatter twice on working copies; inspect its command
|
|
88
|
+
and arguments in the effective config before running them. Use existing authorization; ask only when executing that
|
|
89
|
+
command is outside the authorized work. Canonical Check preserves the target and reports `canonical` verification separately
|
|
90
|
+
from the original source results; exit 0/1 compares final canonical bytes with the source, while an error/refusal exits 2.
|
|
91
|
+
Human approval proof, re-parsing, preservation and a two-run fixed point must pass; report any refusal reason to the user.
|
|
92
|
+
- Do not look for or run other alignment scripts or bypass options.
|
|
93
|
+
|
|
94
|
+
## Write and Refactor
|
|
95
|
+
|
|
96
|
+
1. Run `guide --authoring --json` for the intended file before composing a change. For TypeScript it also returns
|
|
97
|
+
`columnSets` (every registered kind with its ordered columns; `approved` marks kinds an approved rule covers; `{i}`
|
|
98
|
+
is a per-position column) and `axisNotes` (what each column means). Python returns source-free representative variants
|
|
99
|
+
with exact legacy approval axes; `semanticKind`/`sourceContext` are descriptive labels, not new approval identities.
|
|
100
|
+
Use the returned sets as the vocabulary for naming cells instead of reading the whole readability contract; open the contract sections cited below only for a judgment the
|
|
101
|
+
vocabulary cannot settle. They are vocabulary, not approval. For an existing file, also run
|
|
102
|
+
`check --source --json` and read its definitions, callers and relevant tests before changing its structure.
|
|
103
|
+
2. Design the rows before writing. For every group of three or more same-role values you are about to write, name the
|
|
104
|
+
column set they share and compose each value as one row of it, instead of writing a convenient shape and aligning
|
|
105
|
+
afterwards: readers with one argument shape, one field per row, `.map` rows over comparable lists, a branch chosen
|
|
106
|
+
in one row. Follow [grid-first authoring](references/readability-contract.md#grid-first-authoring), including its
|
|
107
|
+
limits: no padding wrappers, keep other roles outside the table, preserve behavior, and measure hot paths.
|
|
108
|
+
Then build a boundary inventory for the requested rows: name each cell, its syntax boundary and its comparison region.
|
|
109
|
+
For a member call, inspect `receiver | final . | method | ( | arguments | )`; for a guard, inspect `condition | return | value`.
|
|
110
|
+
Match roles and nesting depth before matching punctuation. Function signatures, bodies and call sites have separate
|
|
111
|
+
regions. Per-field JSDoc alone does not change the role of ordinary data properties: same-role fields
|
|
112
|
+
share one name/colon/type table. The bounded `documented-data-members` kind handles required properties in data-only
|
|
113
|
+
interfaces; documented Port methods and optional capabilities retain their separate boundaries. Read the rule's
|
|
114
|
+
actual scope and a counterexample before applying an exception. For member calls, early returns or nested type members, read
|
|
115
|
+
[column-sharing regions](references/readability-contract.md#column-sharing-regions) before choosing widths or moving rows.
|
|
116
|
+
3. Author the requested implementation or structural improvement. Preserve execution order, public contracts and meaningful
|
|
117
|
+
differences. An overlong exception is a reason to inspect responsibility and data flow, not permission to add abstractions
|
|
118
|
+
just to shorten a column. Structural guidance is advisory; report unsupported patterns instead of inventing approval data.
|
|
119
|
+
An explicit user request for a local layout change can guide this authoring pass; it does not create reusable preset
|
|
120
|
+
approval or bypass the deterministic Align procedure.
|
|
121
|
+
When authoring or normalizing internal imports, follow the target project's import conventions and package
|
|
122
|
+
boundaries; retain relative imports where its current contract requires them. Otherwise use its configured alias
|
|
123
|
+
(such as `@/`) where equivalent resolution is verified. Read its effective compiler configuration, including inherited mappings, and
|
|
124
|
+
runtime/build/test resolvers first. An alias is project-relative, not a machine-absolute path. Preserve external
|
|
125
|
+
package and `node:` specifiers, module evaluation order and unresolved dynamic paths. Path changes belong to
|
|
126
|
+
Write/Refactor, not Align. Report unsupported alias resolution; change resolver configuration only within the
|
|
127
|
+
authorized scope. Verify type checking and affected build, runtime or isolated installation paths before claiming
|
|
128
|
+
migration complete.
|
|
129
|
+
4. For preview, show the proposed diff and distinguish checks run on a proposal from checks run on the target. For an
|
|
130
|
+
authorized application, inspect the diff, run relevant syntax/type and behavior checks, and assess performance-sensitive
|
|
131
|
+
changes with appropriate evidence. Alignment cannot prove behavioral equivalence or performance.
|
|
132
|
+
5. Where approved and requested, use Align below for final whitespace. Then run the final `check --source --json`, passing each
|
|
133
|
+
boundary you named in step 2 as `--expect-axis <axis>` (repeatable). The engine returns `axisExpectation`: `covered`
|
|
134
|
+
(axis, group count, start lines), `missing` (no group carries it) and `unknown` (not an axis name; fix the spelling).
|
|
135
|
+
It never changes the exit status and shows only that a group carries the axis, not that its columns are right.
|
|
136
|
+
Account for every `missing` axis, including unsupported or single-row boundaries. Inspect their actual columns and
|
|
137
|
+
record manual verification or an unresolved coverage gap. Completion requires
|
|
138
|
+
each requested boundary to be verified or explicitly reported unresolved, alongside remaining findings and checks not run.
|
|
139
|
+
When the user or project instructions require issue-history records, use that schema within the authorized scope to
|
|
140
|
+
retain the reproduction, cause, decision and verification evidence, including failed attempts and unresolved work.
|
|
141
|
+
|
|
142
|
+
## Check and Align
|
|
143
|
+
|
|
144
|
+
When using the read-only `readability-inspector`, give it one target file and a report path owned by the calling agent
|
|
145
|
+
or controller. Capture its full report from the host result channel and save it at that path using the caller's existing
|
|
146
|
+
authorized permissions. Keep the inspector read-only. Return only a 3–5 line verdict and the saved path in the main
|
|
147
|
+
conversation.
|
|
148
|
+
|
|
149
|
+
1. Run `check --source --json`. Read `syntax.status` (`valid`, `invalid` or parser `error`), `formatReadiness` (approval
|
|
150
|
+
eligibility; write verification is still `not-run`), `unsupported`, `groups` and their `PASS`, `FIXABLE`,
|
|
151
|
+
`REFUSE` states. This source-only report does not verify a formatter canonical result or authorize a write. A refused
|
|
152
|
+
group stays unchanged by alignment.
|
|
153
|
+
2. Read `guide --json` for applicable approved rules. With no approved rule, report the candidate and missing evidence;
|
|
154
|
+
do not align it by hand, auto-approve it or use a raw writer.
|
|
155
|
+
3. For Check, report findings. For Align preview, run `align --dry-run --json`. For authorized application, inspect the
|
|
156
|
+
dry-run result and then run `align --json`; report refusal reasons without falling back to `format` or Refactor.
|
|
157
|
+
4. After application, rerun `check --source --json`, reconcile user-requested boundaries with the final returned axes, and report
|
|
158
|
+
missing coverage alongside the remaining `misaligned` count. Zero detected groups
|
|
159
|
+
or zero misalignment does not prove that unsupported code follows the requested style. Report only validations actually run.
|
|
160
|
+
|
|
161
|
+
## Rule documents
|
|
162
|
+
|
|
163
|
+
Read the documents in this folder when a judgment is needed.
|
|
164
|
+
|
|
165
|
+
- `references/charter.md` — principles and safety invariants
|
|
166
|
+
- `references/grammar.md` — grammar units, patterns, roles, paragraphs (split with a blank line when the property differs)
|
|
167
|
+
- `references/readability-contract.md` — confirmed rules, grid-first authoring, discarded rules, column-check order
|
|
168
|
+
- `references/typescript-grid-template.md` — per-role TypeScript table templates
|
|
169
|
+
|
|
170
|
+
## Authoring hooks
|
|
171
|
+
|
|
172
|
+
The plugin defaults to `check` mode: inspect session changes and report without writing source. `refactor` mode also
|
|
173
|
+
keeps hooks read-only; the agent owns structural edits. `off` skips hook inspection. Only the user's explicit
|
|
174
|
+
`boxbox mode align` choice enables PostToolUse alignment, through the approved horizontal-whitespace pipeline and
|
|
175
|
+
after-write recheck. A refusal leaves that file unchanged. Stop always inspects without writing and can request at
|
|
176
|
+
most one continuation. Project mode overrides the global default; a valid `BOXBOX_MODE` overrides both for the process.
|
|
177
|
+
Do not select `align` mode merely because a user requested a Check or Refactor workflow.
|
|
178
|
+
|
|
179
|
+
The npm-installed launcher requires the host's plugin data folder and writes versioned receipts there. Direct CLI
|
|
180
|
+
hooks installed with `hooks install` can fall back to `.boxbox/hooks` when the host provides no data folder. Receipts
|
|
181
|
+
record observed mode, coverage and successful alignment; they never approve rules. A failing PostToolUse report names
|
|
182
|
+
up to three affected files and suppresses duplicate matched-tool feedback until the observed source snapshot changes;
|
|
183
|
+
Stop still performs its read-only completion check. Automatic hooks cover session changes; editor/pre-commit/CI
|
|
184
|
+
canonical checks use the default variant in Running the engine.
|
|
185
|
+
|
|
186
|
+
An update notice is informational. For a user-requested public update, relay `npx boxbox-plugin`; use
|
|
187
|
+
`boxbox update --source <checkout>` only as an explicit development override. Its installed-version check must succeed
|
|
188
|
+
for the selected host, and the host must restart. An optional `statusline` is one row, separate from monitoring.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# BoxBox Charter
|
|
2
|
+
|
|
3
|
+
Status: confirmed (M1.8 S1) · Date: 2026-09-30
|
|
4
|
+
|
|
5
|
+
This document is **layer 0** of the five normative layers. Grammar (layer 1), surface forms (layer 2), conventions (layer 3) and the example book (layer 4) all sit below it, and when they conflict with this document, this document wins. Only maintainers may change it.
|
|
6
|
+
|
|
7
|
+
## Core (generating source)
|
|
8
|
+
|
|
9
|
+
The three lines below are copied verbatim to the top of `SKILL.md` and into the marked section of a user repository's `AGENTS.md`. When the wording changes, the copies must change with it (pinned by a test in M1.8 S7).
|
|
10
|
+
|
|
11
|
+
<!-- boxbox:core:begin -->
|
|
12
|
+
1. Write same-role code in the same pattern. Code is language.
|
|
13
|
+
2. Simplify first, align second. Never cover a reducible difference with whitespace.
|
|
14
|
+
3. Fix automatically only whitespace, and only when certain. Suggest everything else.
|
|
15
|
+
<!-- boxbox:core:end -->
|
|
16
|
+
|
|
17
|
+
## 1. Principle — readability
|
|
18
|
+
|
|
19
|
+
Let the reader follow the flow from top to bottom and compare same-role code vertically. Every rule in this product exists for this one sentence.
|
|
20
|
+
|
|
21
|
+
## 2. Values — code is language
|
|
22
|
+
|
|
23
|
+
- Code is made of **words** (names), **sentences** (one line's declaration, call or condition), **paragraphs** (groups of sentences with one purpose) and **texts** (files).
|
|
24
|
+
- Write same-role sentences in **the same pattern**. Uniformity does not mean fitting every line to the longest one; it means giving the same role **the same minimal composition**.
|
|
25
|
+
- Symbols such as `=`, `:`, `from` and `(` are **particles** that mark roles inside a sentence. Alignment lines up the particles of the same pattern vertically so the reader can scan roles column by column.
|
|
26
|
+
- The same grammar serves both **guiding what gets written** (GUIDE) and **checking it** (REVIEW). The two must never look at different standards.
|
|
27
|
+
- We also know the limits of this metaphor. Understanding code relies mainly on reasoning, not on language ability. So patterns are defined by **role**, not by English word order, and the goal is to reduce the reader's reasoning load.
|
|
28
|
+
|
|
29
|
+
## 3. Identity — a deterministic program
|
|
30
|
+
|
|
31
|
+
- boxbox is a **deterministic program**, and the agent plugin is a channel that calls it. Rules are set by the program and by human approval, not by an agent's judgment.
|
|
32
|
+
- So it has what a program needs: a version, a config schema, exit codes, and a CLI that runs without an agent (CI, hooks, editors).
|
|
33
|
+
- Users can change **conventions** (layer 3) and **examples** (layer 4) to their taste. **Invariants** (section 5) can be changed by no one. Users write only one file themselves, the config; everything else is written by tools and approved by people.
|
|
34
|
+
|
|
35
|
+
## 4. Order — simplify first, align second, verify always
|
|
36
|
+
|
|
37
|
+
*Simplify first. Align second. Verify always. — Follow the structure. Don't force it.*
|
|
38
|
+
|
|
39
|
+
- Alignment is not the goal. A structure lengthened by an exceptional element is first removed, wrapped inward or normalized.
|
|
40
|
+
- Only real, irreducible information is accepted as cell data and aligned at minimum width.
|
|
41
|
+
- Covering a reducible difference with whitespace alignment, or letting whitespace pull semantic units apart, is a failure.
|
|
42
|
+
|
|
43
|
+
## 5. Invariants — unchangeable and not configurable
|
|
44
|
+
|
|
45
|
+
An attempt to turn off any item in this list through configuration is not silently ignored; it is a schema error. An item with an empty "enforced at" exists only as a documented promise, and moving it into code is roadmap work.
|
|
46
|
+
|
|
47
|
+
| # | Invariant | Enforced at |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| 1 | **Only whitespace changes.** Automatic fixes touch only spaces, tabs and line breaks. Non-whitespace tokens, strings and comments must be byte-identical | `common/09_preservation.ts`, `typescript/16_token-preservation.ts`, `test/mutation.test.ts` |
|
|
50
|
+
| 2 | **Output is re-parsed.** A fix is written only when the language's official parser rereads it with the same diagnostics | `common/08_parse-check.ts`, `typescript/07_align-tables.ts` |
|
|
51
|
+
| 3 | **When uncertain, do not touch.** `REFUSE` when it cannot be split losslessly, `UNCERTAIN` when classification is ambiguous. A missed fix is better than a wrong fix | `REFUSE`: across the engine · `UNCERTAIN`: not implemented |
|
|
52
|
+
| 4 | **Automatic fixes only for 3+ rows.** Two-row pairs are only suggested. The only exceptions are user-approved `=` declaration tables and imports | `common/06_table-engine.ts`, `typescript/13_grid-kinds.ts` `minRows` |
|
|
53
|
+
| 5 | **Idempotent.** Running twice gives the same result | per-language alignment tests (`test/lang-harness.ts`) |
|
|
54
|
+
| 6 | **Only relative placement is stored.** Width is always the minimum that table needs, and tables never share widths | `common/06_table-engine.ts` |
|
|
55
|
+
| 7 | **Deterministic.** The same input and config give the same result. There is no LLM and no similarity search on the check/fix path. An LLM is used only to read guidance or to pre-sort the review queue | design rule (no reproduction test) |
|
|
56
|
+
| 8 | **Only humans approve.** Evidence never becomes a rule by itself. Agents only investigate and propose | read-only agents: `test/plugin-contract.test.ts` · approval flow: not implemented |
|
|
57
|
+
| 9 | **Layer priority:** safety > project > user > preset > base. Nothing overrides safety | not implemented (M1.8 S3–S5) |
|
|
58
|
+
| 10 | **Single source, thin adapters.** The engine, grammar and conventions live in one place; hosts differ only in install location and manifest. The result JSON and pattern keys are versioned public contracts | `common/24_result-json.ts`, `common/02_signature.ts` |
|
|
59
|
+
| 11 | **Runs last, after the formatter.** The canonical form of a file is `boxbox(formatter(file))`, and rerunning must not change it | not implemented (M2) |
|
|
60
|
+
|
|
61
|
+
## 6. What this document does not do
|
|
62
|
+
|
|
63
|
+
- It does not define concrete patterns, roles or rules → grammar (`grammar.md`), conventions (`presets/`, `.boxbox/rules/`).
|
|
64
|
+
- It does not list the order in which tools are called → `SKILL.md`.
|
|
65
|
+
- It does not describe the product structure or roadmap → `docs/development/design.md`.
|
|
66
|
+
|
|
67
|
+
## 7. How to change it
|
|
68
|
+
|
|
69
|
+
1. First write the reason and evidence for the change in `docs/development/plan.md`.
|
|
70
|
+
2. A maintainer approves it.
|
|
71
|
+
3. When an invariant changes, change the code and tests that enforce it in the same change.
|
|
72
|
+
4. When the three core lines change, change the copies (`SKILL.md`, the marked section of the user's `AGENTS.md`) in the same change.
|