atris 3.74.2 → 3.74.4
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/atris/atris.md +9 -0
- package/atris.md +9 -0
- package/commands/business.js +9 -3
- package/commands/gm.js +8 -1
- package/commands/member.js +5 -1
- package/commands/task.js +8 -2
- package/commands/taste.js +2 -1
- package/commands/zen.js +1 -1
- package/lib/auto-lessons.js +3 -1
- package/lib/improve-vitals-html.js +12 -3
- package/lib/orb-scorecard.js +9 -2
- package/lib/yes-lock.js +36 -9
- package/package.json +1 -1
package/atris/atris.md
CHANGED
|
@@ -60,6 +60,15 @@ Labels used below:
|
|
|
60
60
|
- `guarded`: checked by code or a pre-commit hook; bypassing is a bug
|
|
61
61
|
- `expected`: convention; honor it or stop
|
|
62
62
|
|
|
63
|
+
## values
|
|
64
|
+
|
|
65
|
+
Four values decide close calls. When a rule above does not settle it, these do.
|
|
66
|
+
|
|
67
|
+
- **Acceleration.** Progress compounds. Land small working changes early and often so the next agent builds on them today, not next week.
|
|
68
|
+
- **Accuracy.** A win counts only when it is checked. Prove it with a test that failed before and passes after, or a second builder's review when the stakes call for it. Faster tests make faster work.
|
|
69
|
+
- **Ambition.** Take one more level. When the small fix is done, look for the bigger version of the same win.
|
|
70
|
+
- **Agency.** Own the missing piece. A bug, a broken tool, or a gap in your way is yours to fix or file, not a reason to stop.
|
|
71
|
+
|
|
63
72
|
## taste
|
|
64
73
|
|
|
65
74
|
What you ship should not read as generated. The test: if someone said "an AI made this," would they believe it instantly? If yes, that is the bug. The model has no words for restraint and it falls into gravity wells. Beat both.
|
package/atris.md
CHANGED
|
@@ -82,6 +82,15 @@ Labels used below:
|
|
|
82
82
|
- `guarded`: checked by code or a pre-commit hook; bypassing is a bug
|
|
83
83
|
- `expected`: convention; honor it or stop
|
|
84
84
|
|
|
85
|
+
## values
|
|
86
|
+
|
|
87
|
+
Four values decide close calls. When a rule above does not settle it, these do.
|
|
88
|
+
|
|
89
|
+
- **Acceleration.** Progress compounds. Land small working changes early and often so the next agent builds on them today, not next week.
|
|
90
|
+
- **Accuracy.** A win counts only when it is checked. Prove it with a test that failed before and passes after, or a second builder's review when the stakes call for it. Faster tests make faster work.
|
|
91
|
+
- **Ambition.** Take one more level. When the small fix is done, look for the bigger version of the same win.
|
|
92
|
+
- **Agency.** Own the missing piece. A bug, a broken tool, or a gap in your way is yours to fix or file, not a reason to stop.
|
|
93
|
+
|
|
85
94
|
## taste
|
|
86
95
|
|
|
87
96
|
What you ship should not read as generated. The test: if someone said "an AI made this," would they believe it instantly? If yes, that is the bug. The model has no words for restraint and it falls into gravity wells. Beat both.
|
package/commands/business.js
CHANGED
|
@@ -1214,8 +1214,14 @@ async function startBusinessWorkspace(...args) {
|
|
|
1214
1214
|
return state;
|
|
1215
1215
|
}
|
|
1216
1216
|
|
|
1217
|
+
// Local calendar day; toISOString is UTC and dates evening work tomorrow
|
|
1218
|
+
// anywhere west of Greenwich.
|
|
1219
|
+
function localDay(now = new Date()) {
|
|
1220
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1217
1223
|
function defaultSharePath(cwd, role) {
|
|
1218
|
-
const stamp =
|
|
1224
|
+
const stamp = localDay();
|
|
1219
1225
|
const roleSlug = slugifyName(role || 'collaborator');
|
|
1220
1226
|
return path.join(cwd, 'atris', 'reports', `${stamp}-share-${roleSlug}.md`);
|
|
1221
1227
|
}
|
|
@@ -1494,7 +1500,7 @@ async function onboardBusiness(...flags) {
|
|
|
1494
1500
|
const intakeRel = path.relative(cwd, intakePath).replace(/\\/g, '/');
|
|
1495
1501
|
const linksRel = path.relative(cwd, linksPath).replace(/\\/g, '/');
|
|
1496
1502
|
const sourcesRel = path.relative(cwd, sourcesPath).replace(/\\/g, '/');
|
|
1497
|
-
const today =
|
|
1503
|
+
const today = localDay();
|
|
1498
1504
|
|
|
1499
1505
|
const briefSlug = `${bizMeta.slug}-starter-brief`;
|
|
1500
1506
|
const briefPath = path.join(cwd, 'atris', 'wiki', 'briefs', `${briefSlug}.md`);
|
|
@@ -2803,7 +2809,7 @@ async function createBusinessInternal(name, flags = [], mode = 'auto') {
|
|
|
2803
2809
|
description ? `\n> ${description}\n` : '',
|
|
2804
2810
|
'\n## The Business\n\n[What problem does this solve?]\n',
|
|
2805
2811
|
'## Revenue Model\n\n[How does this make money?]\n',
|
|
2806
|
-
`---\n*Created: ${
|
|
2812
|
+
`---\n*Created: ${localDay()}*\n`,
|
|
2807
2813
|
].join(''));
|
|
2808
2814
|
console.log(` Local scaffold: ${bizDir}/`);
|
|
2809
2815
|
}
|
package/commands/gm.js
CHANGED
|
@@ -287,6 +287,12 @@ function pickSeedPlayer(workspaceRoot, tasks, args = []) {
|
|
|
287
287
|
return null;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
// Local calendar day; toISOString is UTC and stamps an evening mission
|
|
291
|
+
// with tomorrow anywhere west of Greenwich.
|
|
292
|
+
function localDay(now = new Date()) {
|
|
293
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
290
296
|
function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
291
297
|
if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
|
|
292
298
|
if (!fs.existsSync(path.join(workspaceRoot, 'atris'))) return { tasks, seeded: null };
|
|
@@ -303,7 +309,7 @@ function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
|
303
309
|
assigned_to: player,
|
|
304
310
|
delegate_via: 'agentxp_gm',
|
|
305
311
|
auto_seeded_by: 'atris gm',
|
|
306
|
-
created_for_day:
|
|
312
|
+
created_for_day: localDay(),
|
|
307
313
|
},
|
|
308
314
|
});
|
|
309
315
|
taskDb.noteTask(db, {
|
|
@@ -1951,6 +1957,7 @@ async function gmCommand(...args) {
|
|
|
1951
1957
|
|
|
1952
1958
|
module.exports = {
|
|
1953
1959
|
displayCommand,
|
|
1960
|
+
ensureStarterMission,
|
|
1954
1961
|
gmCommand,
|
|
1955
1962
|
pickClub,
|
|
1956
1963
|
playAction,
|
package/commands/member.js
CHANGED
|
@@ -3385,7 +3385,10 @@ function logDateForScan(relativePath, filePath) {
|
|
|
3385
3385
|
const named = dateFromLogPath(relativePath);
|
|
3386
3386
|
if (named) return named;
|
|
3387
3387
|
try {
|
|
3388
|
-
|
|
3388
|
+
// Local calendar day, like the dated journal names beside it;
|
|
3389
|
+
// toISOString is UTC and calls an evening write tomorrow's.
|
|
3390
|
+
const mtime = fs.statSync(filePath).mtime;
|
|
3391
|
+
return `${mtime.getFullYear()}-${String(mtime.getMonth() + 1).padStart(2, '0')}-${String(mtime.getDate()).padStart(2, '0')}`;
|
|
3389
3392
|
} catch {
|
|
3390
3393
|
return null;
|
|
3391
3394
|
}
|
|
@@ -12015,6 +12018,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
12015
12018
|
}
|
|
12016
12019
|
|
|
12017
12020
|
module.exports = {
|
|
12021
|
+
logDateForScan,
|
|
12018
12022
|
memberRunDefaultWallSeconds,
|
|
12019
12023
|
memberRunWantsReferee,
|
|
12020
12024
|
proposalPromptForGoal,
|
package/commands/task.js
CHANGED
|
@@ -5857,7 +5857,7 @@ function delegateTask(args, options = {}) {
|
|
|
5857
5857
|
assigned_to: owner,
|
|
5858
5858
|
delegate_via: via,
|
|
5859
5859
|
swarlo_channel: via === 'swarlo' ? String(tag || 'tasks') : null,
|
|
5860
|
-
created_for_day:
|
|
5860
|
+
created_for_day: localDay(),
|
|
5861
5861
|
owner_resolution: ownerResolution.reason,
|
|
5862
5862
|
};
|
|
5863
5863
|
if (ownerResolution.requested_owner) metadata.requested_owner = ownerResolution.requested_owner;
|
|
@@ -6023,6 +6023,12 @@ function cmdKeep(args) {
|
|
|
6023
6023
|
console.log(`put away ${count} ${noun} that were finished or sitting still.`);
|
|
6024
6024
|
}
|
|
6025
6025
|
|
|
6026
|
+
// Local calendar day; toISOString is UTC and calls the evening tomorrow
|
|
6027
|
+
// anywhere west of Greenwich.
|
|
6028
|
+
function localDay(now = new Date()) {
|
|
6029
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
6030
|
+
}
|
|
6031
|
+
|
|
6026
6032
|
function cmdDay(args) {
|
|
6027
6033
|
const kept = require('../lib/task-list-keeper').keepWorkspaceTaskList(process.cwd());
|
|
6028
6034
|
if (keptCount(kept)) autoRenderTodoFromDb(process.cwd());
|
|
@@ -6042,7 +6048,7 @@ function cmdDay(args) {
|
|
|
6042
6048
|
failed: (projection.tasks || []).filter(task => task.status === 'failed').length,
|
|
6043
6049
|
stale_failed: staleFailed.length,
|
|
6044
6050
|
};
|
|
6045
|
-
const date =
|
|
6051
|
+
const date = localDay();
|
|
6046
6052
|
if (wantsJson(args)) {
|
|
6047
6053
|
printJson({
|
|
6048
6054
|
ok: true,
|
package/commands/taste.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { addTaste, listTaste } = require('../lib/taste-lessons');
|
|
4
|
+
const { localDay } = require('../lib/local-day');
|
|
4
5
|
|
|
5
6
|
const VALID_FLAGS = new Set(['--why', '--scope', '--example']);
|
|
6
7
|
|
|
@@ -86,7 +87,7 @@ function tasteCommand(args = [], options = {}) {
|
|
|
86
87
|
why,
|
|
87
88
|
scope,
|
|
88
89
|
example,
|
|
89
|
-
added:
|
|
90
|
+
added: localDay(),
|
|
90
91
|
root,
|
|
91
92
|
});
|
|
92
93
|
stdout.write(`saved the operator's ${entry.verdict} verdict for "${entry.subject}".\n`);
|
package/commands/zen.js
CHANGED
|
@@ -143,7 +143,7 @@ function printApply(result, out) {
|
|
|
143
143
|
}
|
|
144
144
|
line(dim(` ${teamLine(result.team_files)}`));
|
|
145
145
|
if (result.saved.length) {
|
|
146
|
-
line(dim('
|
|
146
|
+
line(dim(' nothing was thrown away. see what zen saved: git for-each-ref refs/archive refs/heads/zen-backup'));
|
|
147
147
|
}
|
|
148
148
|
line('');
|
|
149
149
|
}
|
package/lib/auto-lessons.js
CHANGED
|
@@ -134,7 +134,9 @@ function writeJsonAtomic(filePath, value) {
|
|
|
134
134
|
function isoDate(now) {
|
|
135
135
|
const date = now instanceof Date ? now : new Date(now || Date.now());
|
|
136
136
|
if (Number.isNaN(date.getTime())) throw new AutoLessonsError('cannot file automatic lessons with an invalid date');
|
|
137
|
-
|
|
137
|
+
// Local calendar day; toISOString is UTC and dates an evening lesson
|
|
138
|
+
// tomorrow anywhere west of Greenwich.
|
|
139
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
function typedLesson(signal, options = {}) {
|
|
@@ -35,13 +35,22 @@ function commitsSinceMidnight(root) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Local calendar day (YYYY-MM-DD) of a time, or null. Close events are stored
|
|
39
|
+
// as UTC ISO times, and the UTC day is already tomorrow in the evening west
|
|
40
|
+
// of Greenwich, so "today" is compared as local days on both sides.
|
|
41
|
+
function localDay(value) {
|
|
42
|
+
const d = value instanceof Date ? value : new Date(value || NaN);
|
|
43
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
44
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
function closedToday(cwd, now) {
|
|
39
48
|
try {
|
|
40
49
|
const close = require('../commands/close');
|
|
41
|
-
const today = now
|
|
50
|
+
const today = localDay(now);
|
|
42
51
|
return close.foldEvents(close.readEvents(cwd), { now })
|
|
43
|
-
.filter((f) => (f.status === 'closed' &&
|
|
44
|
-
|| (f.status === 'dissolved' &&
|
|
52
|
+
.filter((f) => (f.status === 'closed' && localDay(f.closed_at) === today)
|
|
53
|
+
|| (f.status === 'dissolved' && localDay(f.dissolved_at) === today))
|
|
45
54
|
.map((f) => ({ what: f.what, status: f.status }));
|
|
46
55
|
} catch {
|
|
47
56
|
return [];
|
package/lib/orb-scorecard.js
CHANGED
|
@@ -115,6 +115,13 @@ function pairOrbRuns(records) {
|
|
|
115
115
|
return { runs, orphans };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Local calendar day (YYYY-MM-DD). Orb picks are dated by the day they were
|
|
119
|
+
// made; toISOString() is the UTC day, already tomorrow in the evening west of
|
|
120
|
+
// Greenwich.
|
|
121
|
+
function localDay(date) {
|
|
122
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
function readOrbScorecard(root, { days = DEFAULT_DAYS, now = Date.now() } = {}) {
|
|
119
126
|
const windowDays = Number(days);
|
|
120
127
|
if (!Number.isInteger(windowDays) || windowDays < 1) {
|
|
@@ -125,8 +132,8 @@ function readOrbScorecard(root, { days = DEFAULT_DAYS, now = Date.now() } = {})
|
|
|
125
132
|
|
|
126
133
|
const scorecard = emptyOrbScorecard(windowDays);
|
|
127
134
|
const cutoffMs = nowMs - (windowDays * DAY_MS);
|
|
128
|
-
const cutoffDay = new Date(cutoffMs)
|
|
129
|
-
const today = new Date(nowMs)
|
|
135
|
+
const cutoffDay = localDay(new Date(cutoffMs));
|
|
136
|
+
const today = localDay(new Date(nowMs));
|
|
130
137
|
scorecard.picks = readOrbPicks(root, cutoffDay, today);
|
|
131
138
|
|
|
132
139
|
const indexPath = path.join(root, '.atris', 'state', 'orb-runs', 'index.jsonl');
|
package/lib/yes-lock.js
CHANGED
|
@@ -674,21 +674,42 @@ const PUSH_HINT = 'a branch push runs without a yes as: git push origin <branch>
|
|
|
674
674
|
|
|
675
675
|
// git reads, plus local work that cannot move a ref other than the branch it is on
|
|
676
676
|
const GIT_SEALED_READS = new Set(['status', 'diff', 'log', 'show', 'blame', 'rev-parse', 'ls-files', 'grep', 'describe', 'rev-list', 'merge-base', 'cat-file', 'ls-tree', 'show-ref', 'for-each-ref', 'shortlog', 'check-ignore', 'help', 'version']);
|
|
677
|
-
const GIT_SEALED_LOCAL = new Set(['add', 'rm', 'mv', 'restore']);
|
|
677
|
+
const GIT_SEALED_LOCAL = new Set(['add', 'rm', 'mv', 'restore', 'update-index', 'write-tree']);
|
|
678
678
|
const GIT_SEALED_GLOBALS = new Set(['--no-pager', '-P', '--no-optional-locks', '--literal-pathspecs', '--version', '--help', '-h']);
|
|
679
679
|
// the shape of a branch name git accepts, without the corners that read as something else
|
|
680
680
|
const BRANCH_NAME = /^(?!-)(?!\.)(?!.*\.\.)(?!.*\/\/)(?!.*\/\.)(?!.*@\{)(?!.*\.lock(?:\/|$))[A-Za-z0-9._/-]+(?<![./])$/;
|
|
681
681
|
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
// (
|
|
685
|
-
//
|
|
682
|
+
// -c a woken run may pass: a value that only turns something off, or a key
|
|
683
|
+
// that only changes how output looks. The engines harden their own git calls
|
|
684
|
+
// this way (hooks, fsmonitor, askpass, protocols, gc all off). A -c that
|
|
685
|
+
// names a program, path, command, alias, helper, pager, editor, or route is
|
|
686
|
+
// held, since git runs that program with its own folder first on PATH, where
|
|
687
|
+
// a git inside it skips the wrapper; so are the keys whose value is a name,
|
|
688
|
+
// URL, or refspec (alias, remote, url, push, include, credential, branch
|
|
689
|
+
// routing), where even an empty value can reroute (url.<x>.insteadOf=).
|
|
690
|
+
const GIT_DISPLAY_KEYS = new Set(['format.pretty', 'log.showsignature', 'status.showuntrackedfiles', 'core.quotepath', 'core.safecrlf', 'safe.barerepository', 'color.ui']);
|
|
691
|
+
const GIT_ROUTE_KEYS = /^(alias|remote|url|push|include|includeif|credential)\.|^branch\..+\.(remote|pushremote|merge)$/;
|
|
692
|
+
|
|
693
|
+
function sealedGitConfigOk(pair) {
|
|
694
|
+
const eq = String(pair).indexOf('=');
|
|
695
|
+
const key = (eq < 0 ? String(pair) : String(pair).slice(0, eq)).trim().toLowerCase();
|
|
696
|
+
const value = eq < 0 ? 'true' : String(pair).slice(eq + 1).trim();
|
|
697
|
+
if (GIT_DISPLAY_KEYS.has(key)) return true;
|
|
698
|
+
if (key === 'core.hookspath') return value === '/dev/null';
|
|
699
|
+
if (GIT_ROUTE_KEYS.test(key)) return false;
|
|
700
|
+
return /^(|false|never|0|no|off)$/i.test(value);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Only -C, -c that turns something off, and a few output switches go before
|
|
704
|
+
// the command. --config-env and --exec-path are held.
|
|
686
705
|
function sealedGitPrefix(args) {
|
|
687
706
|
const prefix = [];
|
|
688
707
|
for (let i = 0; i < args.length; i += 1) {
|
|
689
708
|
const a = args[i];
|
|
690
|
-
if (a === '-C') {
|
|
691
|
-
|
|
709
|
+
if (a === '-C' || a === '-c') {
|
|
710
|
+
const value = String(args[i + 1] === undefined ? '' : args[i + 1]);
|
|
711
|
+
if (a === '-c' && !sealedGitConfigOk(value)) return { bad: `-c ${value}` };
|
|
712
|
+
prefix.push(a, value);
|
|
692
713
|
i += 1;
|
|
693
714
|
continue;
|
|
694
715
|
}
|
|
@@ -726,9 +747,11 @@ function sealedGit(args, ctx) {
|
|
|
726
747
|
if (!p.sub) return pass();
|
|
727
748
|
if (GIT_SEALED_READS.has(p.sub) || GIT_SEALED_LOCAL.has(p.sub)) return pass();
|
|
728
749
|
// the questions below are asked of the repo the command names
|
|
729
|
-
const repo = ctx.forRepo(p.prefix.filter((a, i, all) => a === '-C' || all[i - 1] === '-C'));
|
|
750
|
+
const repo = ctx.forRepo(p.prefix.filter((a, i, all) => a === '-C' || a === '-c' || all[i - 1] === '-C' || all[i - 1] === '-c'));
|
|
730
751
|
const rest = p.rest;
|
|
731
752
|
switch (p.sub) {
|
|
753
|
+
// a new repo has no remote; a template or a git folder elsewhere could bring hooks
|
|
754
|
+
case 'init': return rest.some(a => /^--(template|separate-git-dir)(=|$)/.test(a)) ? hold('git init with a template or a git folder elsewhere can bring hooks') : pass();
|
|
732
755
|
case 'push': return sealedPush(p, repo);
|
|
733
756
|
case 'fetch': return sealedFetch(rest, repo);
|
|
734
757
|
case 'commit': return isMainRef(repo.currentBranch()) ? hold('a commit here would land on master') : pass();
|
|
@@ -764,7 +787,9 @@ function sealedPush(p, repo) {
|
|
|
764
787
|
words.push(a);
|
|
765
788
|
}
|
|
766
789
|
if (words.length !== 2 || words[0] !== 'origin') return hold(`a handed-off job pushes one branch to origin; ${PUSH_HINT}`);
|
|
767
|
-
|
|
790
|
+
// HEAD:<branch> is written out as HEAD:refs/heads/<branch>, so a name that
|
|
791
|
+
// matches a tag on origin can never update the tag
|
|
792
|
+
const head = words[1].match(/^HEAD:(?:refs\/heads\/(?=.))?(?!refs\/)(.+)$/);
|
|
768
793
|
const branch = head ? head[1] : words[1];
|
|
769
794
|
if (!head && (branch.includes(':') || branch.startsWith('refs/'))) return hold(`git push ${words[1]} names more than a branch; ${PUSH_HINT}`);
|
|
770
795
|
if (isMainRef(branch)) return hold(`git push to master lands on master; ${PUSH_HINT}`);
|
|
@@ -964,6 +989,8 @@ const ATRIS_SEALED = {
|
|
|
964
989
|
function sealedAtris(args, missionId) {
|
|
965
990
|
const [cmd = '', sub = ''] = args;
|
|
966
991
|
if (args.length === 1 && ['--version', '-v', '--help', '-h'].includes(cmd)) return pass();
|
|
992
|
+
// the boot read: atris prints the welcome screen for its spec file and stops
|
|
993
|
+
if (args.length === 1 && (cmd === 'atris.md' || cmd.endsWith('/atris.md'))) return pass();
|
|
967
994
|
const allowed = Object.prototype.hasOwnProperty.call(ATRIS_SEALED, cmd) ? ATRIS_SEALED[cmd] : null;
|
|
968
995
|
if (!allowed) return hold(`atris ${cmd} is not on the list a handed-off job may run`);
|
|
969
996
|
const subOk = allowed === true || ['help', '--help', '-h'].includes(sub)
|
package/package.json
CHANGED