roblox-guard 0.4.0 → 0.6.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.md +77 -0
- package/LICENSE +21 -21
- package/README.md +22 -5
- package/package.json +3 -2
- package/scripts/roblox-guard.mjs +178 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Roblox and Luau standards
|
|
2
|
+
|
|
3
|
+
Scope: every `.luau` and `.lua` file in a Roblox project. These rules govern how each script is
|
|
4
|
+
written, not how the project is structured, so they hold under any framework or folder layout.
|
|
5
|
+
|
|
6
|
+
They are active instruction, not background reading. Carry them forward verbatim into any
|
|
7
|
+
summary, handoff, or task note. Never reconstruct them from memory: a half-remembered layout
|
|
8
|
+
rule looks deliberate and is worse than none.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
ROBLOX LUAU SKILL - INVARIANT CARD
|
|
12
|
+
1 Three sections, this order:
|
|
13
|
+
-- // VARIABLES // -- Services > Modules > Objects > Configuration > State Management
|
|
14
|
+
-- // FUNCTIONS // -- definitions only (ModuleScript: Private before Public)
|
|
15
|
+
-- // INITIALIZATION // -- everything that runs
|
|
16
|
+
Omit a section that would be empty. Never write a placeholder header.
|
|
17
|
+
2 Documentation Comments (Luau Comments) - default style, adapts to the project.
|
|
18
|
+
Default block: --[[ ]] above the function, desc > @param > @return.
|
|
19
|
+
Moonwave --[=[ ]=] or --- is equally correct when that is the project's style.
|
|
20
|
+
- Desc <= 3 lines and <= 250 chars, contract-level, states PURPOSE.
|
|
21
|
+
- Desc never names what the body does to get there: no APIs, algorithms,
|
|
22
|
+
collaborators, data structures, or code paths.
|
|
23
|
+
- Desc carries NO volatile content: no numbers, thresholds, tunable names,
|
|
24
|
+
feature names, or anything that needs editing when the body is retuned.
|
|
25
|
+
- Tags use Moonwave syntax: @param <name> <type> -- <description>
|
|
26
|
+
and @return <type> -- <description>. Only when they add what the
|
|
27
|
+
signature cannot show.
|
|
28
|
+
- English preferred. No em dashes or double-hyphen dashes as punctuation
|
|
29
|
+
(the -- in a tag is a separator, not punctuation). No emoji.
|
|
30
|
+
- IN-BODY COMMENTS: banned in code you write. Make names and structure say
|
|
31
|
+
it; put the why in the block above. Never delete an existing one;
|
|
32
|
+
propose removal once, Advisory only.
|
|
33
|
+
- Self-documenting code outranks commentary everywhere.
|
|
34
|
+
- Existing project comment style wins. Recommend this style when the user
|
|
35
|
+
asks to restyle; never impose it.
|
|
36
|
+
3 Server is authoritative. Validate every remote arg: type, range, ownership, rate.
|
|
37
|
+
4 Clean up everything created. Every connection has an owner and a teardown path.
|
|
38
|
+
5 No avoidable per-frame garbage. Never poll what has a signal.
|
|
39
|
+
6 UpdateAsync + backoff. Save on PlayerRemoving. Flush on BindToClose.
|
|
40
|
+
7 Re-validate after every yield: player gone? instance dead? session changed?
|
|
41
|
+
8 Never add --!strict unbidden. Never make a [Beta] feature the production default.
|
|
42
|
+
9 Reuse before writing: project, then stdlib, then engine API. No wrapper or
|
|
43
|
+
abstraction without a caller. But brevity has two hard limits:
|
|
44
|
+
- It NEVER reduces what was asked for. Short because it does less = failed.
|
|
45
|
+
- It NEVER costs readability. One statement per line, descriptive names,
|
|
46
|
+
blank lines kept. Less code means less WORK, not less whitespace.
|
|
47
|
+
10 No deprecated APIs: wait, spawn, delay, tick, lowercase :connect,
|
|
48
|
+
Humanoid:LoadAnimation, SetPrimaryPartCFrame, Body* movers.
|
|
49
|
+
11 Engine facts are cited, not remembered. Confirm a newer API against the
|
|
50
|
+
version dump or an in-Studio probe before relying on it.
|
|
51
|
+
12 User authority outranks these rules. Recommend; never refactor unasked.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Full standards
|
|
55
|
+
|
|
56
|
+
This card is the part that must survive a summary. The complete standards, with the reference
|
|
57
|
+
files behind each rule, live in this repository:
|
|
58
|
+
|
|
59
|
+
| Path | Covers |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `skills/best-practices/SKILL.md` | Writing and refactoring Luau |
|
|
62
|
+
| `skills/code-review/SKILL.md` | Reviewing, auditing, and scoring existing code |
|
|
63
|
+
| `skills/studio-ops/SKILL.md` | Studio MCP, sync toolchains, verifying in a running session |
|
|
64
|
+
|
|
65
|
+
Read the one whose task matches. Each reference file is self-contained; read one, not the set.
|
|
66
|
+
|
|
67
|
+
## The checks run outside you
|
|
68
|
+
|
|
69
|
+
`scripts/roblox-guard.mjs` reports deprecated APIs and out-of-order section headers
|
|
70
|
+
deterministically, with no model involved. Run it on the files you touched before you report
|
|
71
|
+
work as done:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
node scripts/roblox-guard.mjs --check <files>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It exits 1 when a file has findings, and each finding names the line and the replacement.
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Muhammad Andriansyah
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muhammad Andriansyah
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Professional Roblox and Luau standards for AI agents, and a checker that runs wi
|
|
|
9
9
|
[](https://github.com/andrian-syh/roblox-optimum/actions/workflows/ci.yml)
|
|
10
10
|
[](CHANGELOG.md)
|
|
11
11
|
[](LICENSE)
|
|
12
|
+
[](https://www.npmjs.com/package/roblox-guard)
|
|
12
13
|
[](package.json)
|
|
13
14
|
[](package.json)
|
|
14
15
|
|
|
@@ -25,8 +26,8 @@ Claude Code, and as a project instruction file everywhere else. [AGENTS.md](AGEN
|
|
|
25
26
|
ready to copy, along with a matching file for Cursor, Windsurf, Cline, Kiro, Qoder, Copilot,
|
|
26
27
|
Qwen Code, and Antigravity.
|
|
27
28
|
|
|
28
|
-
**The checker** is [`
|
|
29
|
-
dependencies. It reads Luau and reports the same findings whoever wrote the file, so it works as
|
|
29
|
+
**The checker** is [`roblox-guard`](https://www.npmjs.com/package/roblox-guard) on npm, one Node
|
|
30
|
+
file with no dependencies. It reads Luau and reports the same findings whoever wrote the file, so it works as
|
|
30
31
|
an agent hook, a commit hook, or a CI step. It never asks a model anything, which is why it still
|
|
31
32
|
holds after a long session has been summarized and the rules have fallen out of context.
|
|
32
33
|
|
|
@@ -54,9 +55,15 @@ Qwen Code:
|
|
|
54
55
|
qwen extensions install https://github.com/andrian-syh/roblox-optimum
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
Cursor, Antigravity, and
|
|
58
|
-
OpenCode
|
|
59
|
-
|
|
58
|
+
Cursor, Antigravity, Kiro, and Copilot CLI install it as a plugin from this repository. For those,
|
|
59
|
+
plus OpenCode or a plain commit hook, see **[INSTALL.md](INSTALL.md)**, which also states plainly
|
|
60
|
+
what each setup can and cannot do.
|
|
61
|
+
|
|
62
|
+
The checker on its own needs no repository at all:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
npx roblox-guard --check src/**/*.luau
|
|
66
|
+
```
|
|
60
67
|
|
|
61
68
|
## Skills
|
|
62
69
|
|
|
@@ -113,6 +120,16 @@ or synced, belong to a project rather than to a person, and are resolved per ses
|
|
|
113
120
|
Deprecated APIs and out-of-order section headers. Where the agent can receive them, findings go
|
|
114
121
|
straight back to it and it fixes its own output before you see it.
|
|
115
122
|
|
|
123
|
+
```
|
|
124
|
+
$ npx roblox-guard --check CoinService.luau
|
|
125
|
+
Roblox standards check failed for CoinService.luau:
|
|
126
|
+
- Line 5: :connect() is deprecated. Use :Connect().
|
|
127
|
+
- Line 6: wait() is deprecated. Use task.wait().
|
|
128
|
+
- Line 8: Humanoid:LoadAnimation() is deprecated. Use Animator:LoadAnimation().
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Exit 1, so a commit hook or a build step stops on it.
|
|
132
|
+
|
|
116
133
|
The check is deliberately conservative, because a wrong complaint gets a guard switched off:
|
|
117
134
|
|
|
118
135
|
- Comments and string literals are excluded, so a rule quoted in prose is never reported as a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roblox-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Deterministic Roblox and Luau standards checks: deprecated APIs and out-of-order section headers, as an agent hook, a commit hook, or a CI step.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"roblox",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"roblox-guard": "scripts/roblox-guard.mjs"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"scripts/roblox-guard.mjs"
|
|
20
|
+
"scripts/roblox-guard.mjs",
|
|
21
|
+
"AGENTS.md"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"test": "node scripts/roblox-guard.mjs --selftest"
|
package/scripts/roblox-guard.mjs
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* rules have fallen out of the model's context.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
-
import { join, resolve, sep } from "node:path";
|
|
10
|
-
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
9
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
/** The installed package, so `install` can read the standards it ships with. */
|
|
13
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
14
|
|
|
12
15
|
/** Proof that a source file is Roblox rather than another Lua dialect. */
|
|
13
16
|
const ROBLOX_MARKERS =
|
|
@@ -64,10 +67,51 @@ const SKIP_REASON = {
|
|
|
64
67
|
unreadable: "could not be read as a file",
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
/** Marks a file this tool wrote, so it may be replaced without asking. */
|
|
71
|
+
export const GENERATED = "<!-- Generated from AGENTS.md. Edit that file. -->";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Where each agent reads its instructions, the front matter that host expects above the shared
|
|
75
|
+
* body, and the directory whose presence says the host is in use here. An agent that reads
|
|
76
|
+
* plain Markdown takes no front matter; one with no marker of its own is written on request.
|
|
77
|
+
*/
|
|
78
|
+
export const RULE_TARGETS = [
|
|
79
|
+
{
|
|
80
|
+
path: ".cursor/rules/roblox-optimum.mdc",
|
|
81
|
+
marker: ".cursor",
|
|
82
|
+
agent: "Cursor",
|
|
83
|
+
frontMatter: `---
|
|
84
|
+
description: Roblox and Luau coding standards
|
|
85
|
+
globs: ["**/*.luau", "**/*.lua"]
|
|
86
|
+
alwaysApply: false
|
|
87
|
+
---
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
path: ".kiro/steering/roblox-optimum.md",
|
|
92
|
+
marker: ".kiro",
|
|
93
|
+
agent: "Kiro",
|
|
94
|
+
frontMatter: `---
|
|
95
|
+
inclusion: fileMatch
|
|
96
|
+
fileMatchPattern: ["**/*.luau", "**/*.lua"]
|
|
97
|
+
---
|
|
98
|
+
`,
|
|
99
|
+
},
|
|
100
|
+
{ path: "rules/roblox-optimum.md", marker: "rules", agent: "a plugin rules directory", frontMatter: "" },
|
|
101
|
+
{ path: ".windsurf/rules/roblox-optimum.md", marker: ".windsurf", agent: "Windsurf", frontMatter: "" },
|
|
102
|
+
{ path: ".clinerules/roblox-optimum.md", marker: ".clinerules", agent: "Cline", frontMatter: "" },
|
|
103
|
+
{ path: ".qoder/rules/roblox-optimum.md", marker: ".qoder", agent: "Qoder", frontMatter: "" },
|
|
104
|
+
{ path: ".agents/rules/roblox-optimum.md", marker: ".agents", agent: "Antigravity", frontMatter: "" },
|
|
105
|
+
{ path: ".github/copilot-instructions.md", marker: ".github", agent: "GitHub Copilot", frontMatter: "" },
|
|
106
|
+
{ path: "QWEN.md", marker: null, agent: "Qwen Code", frontMatter: "" },
|
|
107
|
+
];
|
|
108
|
+
|
|
67
109
|
const USAGE = `roblox-guard - deterministic Roblox and Luau standards checks
|
|
68
110
|
|
|
69
111
|
Usage:
|
|
70
112
|
roblox-guard --check <file...> Check files. Exit 1 when a file has findings.
|
|
113
|
+
roblox-guard install [--all] Write the standards into this project, for every agent it
|
|
114
|
+
finds. --all writes every agent's file regardless.
|
|
71
115
|
roblox-guard --selftest Run the built-in assertions.
|
|
72
116
|
roblox-guard Read a post-write hook payload on stdin. Exit 2 reports
|
|
73
117
|
findings back to the agent.
|
|
@@ -188,15 +232,21 @@ export function inspect(source) {
|
|
|
188
232
|
}
|
|
189
233
|
|
|
190
234
|
const lines = code.split("\n");
|
|
235
|
+
const deprecated = [];
|
|
236
|
+
|
|
191
237
|
for (const [pattern, name, replacement] of DEPRECATED) {
|
|
192
238
|
for (let k = 0; k < lines.length; k++) {
|
|
193
239
|
if (pattern.test(lines[k])) {
|
|
194
|
-
|
|
240
|
+
deprecated.push({ line: k + 1, text: `Line ${k + 1}: ${name} is deprecated. Use ${replacement}.` });
|
|
195
241
|
break;
|
|
196
242
|
}
|
|
197
243
|
}
|
|
198
244
|
}
|
|
199
245
|
|
|
246
|
+
// Report in reading order. Grouping by pattern is how they are found, not how they are read.
|
|
247
|
+
deprecated.sort((a, b) => a.line - b.line);
|
|
248
|
+
problems.push(...deprecated.map((d) => d.text));
|
|
249
|
+
|
|
200
250
|
return problems;
|
|
201
251
|
}
|
|
202
252
|
|
|
@@ -340,6 +390,109 @@ function runCheck(paths) {
|
|
|
340
390
|
return 0;
|
|
341
391
|
}
|
|
342
392
|
|
|
393
|
+
/** The commit hook, which is the one setup that works whoever wrote the file. */
|
|
394
|
+
const PRE_COMMIT = `#!/bin/sh
|
|
395
|
+
# Installed by roblox-guard. Delete this file to remove it.
|
|
396
|
+
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.luau?$')
|
|
397
|
+
[ -z "$files" ] || npx roblox-guard --check $files
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Whether a path may be written: either it is absent, or this tool wrote what is there.
|
|
402
|
+
* Anyone else's file is left alone, because a standards tool that overwrites work silently
|
|
403
|
+
* has already cost more than it saves.
|
|
404
|
+
*
|
|
405
|
+
* @param path string -- Absolute path to consider.
|
|
406
|
+
* @param mark string -- The line this tool stamps into what it writes.
|
|
407
|
+
* @return boolean
|
|
408
|
+
*/
|
|
409
|
+
function writable(path, mark) {
|
|
410
|
+
if (!existsSync(path)) return true;
|
|
411
|
+
try {
|
|
412
|
+
return readFileSync(path, "utf8").includes(mark);
|
|
413
|
+
} catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Writes the standards into the current project for every agent it can see, and reports the
|
|
420
|
+
* installs it cannot perform itself.
|
|
421
|
+
*
|
|
422
|
+
* @param all boolean -- Write every agent's file, not just the ones with a marker present.
|
|
423
|
+
* @return number -- Process exit code.
|
|
424
|
+
*/
|
|
425
|
+
function runInstall(all) {
|
|
426
|
+
const source = join(PACKAGE_ROOT, "AGENTS.md");
|
|
427
|
+
if (!existsSync(source)) {
|
|
428
|
+
process.stderr.write(`roblox-guard install: AGENTS.md is missing from the package.\n`);
|
|
429
|
+
return 2;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const body = readFileSync(source, "utf8").replace(/\r\n/g, "\n");
|
|
433
|
+
const cwd = process.cwd();
|
|
434
|
+
const written = [];
|
|
435
|
+
const kept = [];
|
|
436
|
+
|
|
437
|
+
for (const target of [{ path: "AGENTS.md", marker: null, frontMatter: "" }, ...RULE_TARGETS]) {
|
|
438
|
+
if (!all && target.marker && !existsSync(join(cwd, target.marker))) continue;
|
|
439
|
+
if (!all && target.path === "QWEN.md") continue;
|
|
440
|
+
|
|
441
|
+
const full = join(cwd, target.path);
|
|
442
|
+
if (!writable(full, GENERATED) && target.path !== "AGENTS.md") {
|
|
443
|
+
kept.push(target.path);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (target.path === "AGENTS.md" && existsSync(full) && !writable(full, GENERATED)) {
|
|
447
|
+
kept.push(target.path);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
452
|
+
writeFileSync(full, `${target.frontMatter}${GENERATED}\n\n${body}`);
|
|
453
|
+
written.push(target.path);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const hook = join(cwd, ".git", "hooks", "pre-commit");
|
|
457
|
+
let hookNote = "";
|
|
458
|
+
if (existsSync(join(cwd, ".git"))) {
|
|
459
|
+
if (writable(hook, "roblox-guard")) {
|
|
460
|
+
mkdirSync(dirname(hook), { recursive: true });
|
|
461
|
+
writeFileSync(hook, PRE_COMMIT, { mode: 0o755 });
|
|
462
|
+
written.push(".git/hooks/pre-commit");
|
|
463
|
+
} else {
|
|
464
|
+
hookNote =
|
|
465
|
+
"\nA pre-commit hook already exists, so it was left alone. To add the check to it:\n" +
|
|
466
|
+
" npx roblox-guard --check $(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.luau?$')\n";
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
process.stdout.write(
|
|
471
|
+
(written.length > 0
|
|
472
|
+
? `roblox-guard installed ${written.length} file(s):\n` + written.map((p) => ` ${p}\n`).join("")
|
|
473
|
+
: "roblox-guard wrote nothing new.\n") +
|
|
474
|
+
(kept.length > 0
|
|
475
|
+
? `\nLeft alone, because something else wrote them:\n` + kept.map((p) => ` ${p}\n`).join("")
|
|
476
|
+
: "") +
|
|
477
|
+
hookNote +
|
|
478
|
+
`
|
|
479
|
+
Agents that install themselves, run whichever you use:
|
|
480
|
+
Claude Code /plugin marketplace add andrian-syh/roblox-optimum
|
|
481
|
+
/plugin install roblox-optimum@andrian-syh
|
|
482
|
+
Codex codex plugin marketplace add andrian-syh/roblox-optimum
|
|
483
|
+
codex plugin add roblox-optimum@andrian-syh
|
|
484
|
+
Copilot CLI copilot plugin marketplace add andrian-syh/roblox-optimum
|
|
485
|
+
copilot plugin install roblox-optimum@andrian-syh
|
|
486
|
+
Qwen Code qwen extensions install ${HOME_PAGE}
|
|
487
|
+
Kiro install a power from ${HOME_PAGE}
|
|
488
|
+
|
|
489
|
+
Standards: ${HOME_PAGE}
|
|
490
|
+
`,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
|
|
343
496
|
/**
|
|
344
497
|
* Tells the agent to re-read the skill once a summary has dropped the rules, and stays
|
|
345
498
|
* silent outside a Roblox project so unrelated sessions are not disturbed.
|
|
@@ -399,6 +552,15 @@ Players.PlayerAdded:Connect(greet)
|
|
|
399
552
|
inspect(good.replace("task.wait(1)", "wait(1)")).some((p) => p.includes("wait()")),
|
|
400
553
|
"bare wait() is caught",
|
|
401
554
|
);
|
|
555
|
+
ok(
|
|
556
|
+
(() => {
|
|
557
|
+
const lines = inspect(`-- // VARIABLES // --\nlocal a = 1\n-- // INITIALIZATION // --\nspawn(function()\n\twait(1)\nend)\nx:connect(f)`)
|
|
558
|
+
.map((p) => Number(p.match(/^Line (\d+)/)?.[1]))
|
|
559
|
+
.filter(Number.isFinite);
|
|
560
|
+
return lines.length === 3 && lines.every((n, k) => k === 0 || n >= lines[k - 1]);
|
|
561
|
+
})(),
|
|
562
|
+
"findings are reported in line order, not pattern order",
|
|
563
|
+
);
|
|
402
564
|
ok(
|
|
403
565
|
!inspect(good).some((p) => p.includes("wait()")),
|
|
404
566
|
"task.wait() is not mistaken for wait()",
|
|
@@ -482,6 +644,17 @@ Players.PlayerAdded:Connect(greet)
|
|
|
482
644
|
"LoadAnimation on an Animator is not reported",
|
|
483
645
|
);
|
|
484
646
|
|
|
647
|
+
ok(
|
|
648
|
+
RULE_TARGETS.every((t) => typeof t.path === "string" && typeof t.frontMatter === "string"),
|
|
649
|
+
"every rule target names a path and its front matter",
|
|
650
|
+
);
|
|
651
|
+
ok(
|
|
652
|
+
new Set(RULE_TARGETS.map((t) => t.path)).size === RULE_TARGETS.length,
|
|
653
|
+
"no two rule targets claim the same path",
|
|
654
|
+
);
|
|
655
|
+
ok(writable(join(ROOT_ABSENT, "nothing.md"), GENERATED), "an absent file may be written");
|
|
656
|
+
ok(!writable("package.json", GENERATED), "a file this tool did not write is left alone");
|
|
657
|
+
|
|
485
658
|
ok(checkFile("notes.txt").status === "not-luau", "a non-Luau path is reported as skipped");
|
|
486
659
|
ok(
|
|
487
660
|
checkFile(join("proj", "Packages", "Cmdr.luau")).status === "vendored",
|
|
@@ -504,6 +677,7 @@ if (invokedDirectly) {
|
|
|
504
677
|
if (mode === "--selftest") selftest();
|
|
505
678
|
else if (mode === "--compact") process.exit(await runCompactReminder());
|
|
506
679
|
else if (mode === "--check") process.exit(runCheck(rest));
|
|
680
|
+
else if (mode === "install") process.exit(runInstall(rest.includes("--all")));
|
|
507
681
|
else if (mode === "--help" || mode === "-h") process.stdout.write(USAGE);
|
|
508
682
|
else if (mode === undefined) process.exit(await runPostToolUse());
|
|
509
683
|
else {
|