atris 3.52.0 → 3.55.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/FOR_AGENTS.md +7 -0
- package/README.md +1 -1
- package/atris/policies/ANTISLOP.md +2 -1
- package/atris/skills/copy-editor/SKILL.md +2 -1
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/engines/SKILL.md +8 -7
- package/atris/skills/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +89 -40
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/ax +592 -91
- package/bin/atris +21 -0
- package/bin/atris.js +580 -378
- package/commands/api-key.js +170 -0
- package/commands/auth.js +453 -43
- package/commands/autoland.js +3 -1
- package/commands/autopilot-front.js +58 -11
- package/commands/avail.js +5 -4
- package/commands/balance.js +55 -0
- package/commands/bench.js +29 -1
- package/commands/brain.js +63 -12
- package/commands/brainstorm.js +18 -0
- package/commands/business.js +149 -1
- package/commands/chat-scan.js +17 -4
- package/commands/close.js +5 -1
- package/commands/computer.js +2 -2
- package/commands/doctor.js +174 -0
- package/commands/dream.js +7 -0
- package/commands/drill.js +34 -2
- package/commands/engine.js +47 -35
- package/commands/errors.js +58 -8
- package/commands/experiments.js +14 -0
- package/commands/feedback.js +61 -2
- package/commands/fleet-report.js +27 -7
- package/commands/fleet.js +98 -11
- package/commands/founder.js +60 -21
- package/commands/human-missions.js +79 -4
- package/commands/improve.js +54 -4
- package/commands/init.js +68 -92
- package/commands/install.js +40 -0
- package/commands/integrations.js +399 -30
- package/commands/interview.js +8 -0
- package/commands/land.js +48 -17
- package/commands/learn.js +29 -4
- package/commands/log.js +51 -0
- package/commands/member.js +68 -10
- package/commands/mission.js +339 -74
- package/commands/now.js +25 -6
- package/commands/orb.js +97 -2
- package/commands/pack.js +83 -9
- package/commands/playbook.js +381 -0
- package/commands/plugin.js +16 -0
- package/commands/radar.js +96 -40
- package/commands/recap.js +128 -21
- package/commands/release.js +102 -2
- package/commands/review.js +5 -10
- package/commands/revisions.js +55 -0
- package/commands/run-front.js +12 -4
- package/commands/scout.js +12 -1
- package/commands/search.js +123 -28
- package/commands/sign.js +2 -2
- package/commands/signup.js +12 -3
- package/commands/site-deploy.js +17 -4
- package/commands/site.js +2 -2
- package/commands/skill-eval.js +289 -0
- package/commands/skill.js +56 -6
- package/commands/social.js +603 -0
- package/commands/spaceship.js +69 -6
- package/commands/stream.js +8 -1
- package/commands/study.js +32 -19
- package/commands/sync.js +28 -22
- package/commands/task.js +418 -60
- package/commands/teach.js +72 -63
- package/commands/terminal.js +25 -9
- package/commands/topup.js +88 -0
- package/commands/truth.js +52 -6
- package/commands/usage.js +91 -0
- package/commands/verify.js +3 -2
- package/commands/version.js +3 -1
- package/commands/voice.js +5 -1
- package/commands/who.js +69 -11
- package/commands/wiki.js +7 -4
- package/commands/wish.js +76 -5
- package/commands/workflow.js +327 -145
- package/commands/worktree.js +1 -0
- package/commands/write.js +5 -0
- package/commands/x-search.js +340 -0
- package/commands/xp.js +5 -1
- package/commands/youtube.js +551 -33
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +10 -2
- package/lib/auto-lessons.js +186 -0
- package/lib/autoland.js +4 -2
- package/lib/chat-log-scan.js +7 -4
- package/lib/cli-json.js +77 -0
- package/lib/cli-scope.js +26 -0
- package/lib/codex-flight.js +1 -0
- package/lib/conductor-artifacts.js +1 -1
- package/lib/context-gatherer.js +11 -0
- package/lib/developer-api.js +116 -0
- package/lib/engine-ask.js +11 -3
- package/lib/engine-registry.js +32 -7
- package/lib/feedback-triage.js +274 -0
- package/lib/first-minute.js +398 -0
- package/lib/fleet.js +115 -34
- package/lib/functional-owner.js +22 -0
- package/lib/known-commands.js +31 -5
- package/lib/member-scaffold.js +197 -0
- package/lib/mission-ledger-compact.js +127 -0
- package/lib/mission-root.js +4 -2
- package/lib/mission-runtime-loop.js +30 -2
- package/lib/next-moves.js +34 -34
- package/lib/noninteractive.js +88 -0
- package/lib/permission-grants.js +10 -1
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/runner-command.js +5 -4
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +22 -2
- package/lib/task-proof.js +75 -9
- package/lib/task-receipt.js +5 -1
- package/lib/verifier-quality.js +69 -0
- package/lib/wish-audit.js +2 -2
- package/lib/wish-delegate.js +10 -1
- package/lib/workspace-scaffold.js +270 -0
- package/package.json +4 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/scripts/outbound-artifact-gate.js +227 -0
- package/utils/update-check.js +15 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { getEngineAdapter } = require('../lib/bench/engines');
|
|
8
|
+
const {
|
|
9
|
+
SKILL_EVAL_SCHEMA,
|
|
10
|
+
appendSkillEvalReceipt,
|
|
11
|
+
changedSkillFiles,
|
|
12
|
+
} = require('../lib/skill-eval-gate');
|
|
13
|
+
|
|
14
|
+
const REVIEW_DIRECTORY_PREFIX = 'atris-review-';
|
|
15
|
+
const RUBRIC_KEYS = Object.freeze([
|
|
16
|
+
'instruction_clarity',
|
|
17
|
+
'trigger_precision',
|
|
18
|
+
'procedural_completeness',
|
|
19
|
+
'safety',
|
|
20
|
+
'verifiability',
|
|
21
|
+
]);
|
|
22
|
+
const JUDGE_ORDER = Object.freeze(['claude', 'codex', 'cursor']);
|
|
23
|
+
|
|
24
|
+
function readFlag(args, name) {
|
|
25
|
+
const prefix = `${name}=`;
|
|
26
|
+
const inline = args.find((arg) => String(arg).startsWith(prefix));
|
|
27
|
+
if (inline) return String(inline).slice(prefix.length);
|
|
28
|
+
const index = args.indexOf(name);
|
|
29
|
+
if (index !== -1 && args[index + 1] && !String(args[index + 1]).startsWith('--')) {
|
|
30
|
+
return String(args[index + 1]);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseArgs(args = []) {
|
|
36
|
+
const valueFlags = new Set(['--judge', '--worker-model', '--timeout']);
|
|
37
|
+
let skillPath = null;
|
|
38
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
39
|
+
const arg = String(args[index]);
|
|
40
|
+
if (valueFlags.has(arg)) {
|
|
41
|
+
index += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (arg.startsWith('--')) continue;
|
|
45
|
+
if (!skillPath) skillPath = arg;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
skillPath,
|
|
49
|
+
judge: readFlag(args, '--judge') || process.env.ATRIS_SKILL_JUDGE || null,
|
|
50
|
+
workerModel: readFlag(args, '--worker-model') || null,
|
|
51
|
+
timeoutMs: Math.max(1000, Number(readFlag(args, '--timeout') || 300) * 1000),
|
|
52
|
+
json: args.includes('--json'),
|
|
53
|
+
help: args.includes('--help') || args.includes('-h') || args.includes('help'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function workerModelFromEnvironment(env = process.env) {
|
|
58
|
+
if (env.ATRIS_WORKER_MODEL) return String(env.ATRIS_WORKER_MODEL).trim();
|
|
59
|
+
if (env.CODEX_SANDBOX || env.CODEX_THREAD_ID) return String(env.CODEX_MODEL || 'codex').trim();
|
|
60
|
+
if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT) {
|
|
61
|
+
return String(env.ATRIS_CLAUDE_MODEL || env.ANTHROPIC_MODEL || 'claude').trim();
|
|
62
|
+
}
|
|
63
|
+
if (env.CURSOR_AGENT) return 'cursor';
|
|
64
|
+
if (env.DEVIN_SESSION_ID) return 'devin';
|
|
65
|
+
if (env.ATRIS_RUNNER_MODEL) return String(env.ATRIS_RUNNER_MODEL).trim();
|
|
66
|
+
return 'human';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function identityFamily(value) {
|
|
70
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
71
|
+
if (/claude|anthropic/.test(normalized)) return 'claude';
|
|
72
|
+
if (/codex|openai|gpt/.test(normalized)) return 'codex';
|
|
73
|
+
if (/cursor/.test(normalized)) return 'cursor';
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveSkillPath(root, requested) {
|
|
78
|
+
if (!requested) throw new Error('a skill path is required');
|
|
79
|
+
let absolute = path.resolve(root, requested);
|
|
80
|
+
try {
|
|
81
|
+
if (fs.statSync(absolute).isDirectory()) absolute = path.join(absolute, 'SKILL.md');
|
|
82
|
+
} catch {
|
|
83
|
+
// A deleted SKILL.md can still be evaluated from the branch diff.
|
|
84
|
+
}
|
|
85
|
+
const relative = path.relative(root, absolute).replace(/\\/g, '/');
|
|
86
|
+
if (!relative || relative === '..' || relative.startsWith('../') || path.basename(relative) !== 'SKILL.md') {
|
|
87
|
+
throw new Error('the skill path must name a SKILL.md inside this workspace');
|
|
88
|
+
}
|
|
89
|
+
return { absolute, relative };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function gitOutput(root, args) {
|
|
93
|
+
const result = spawnSync('git', args, {
|
|
94
|
+
cwd: root,
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
timeout: 10000,
|
|
97
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
98
|
+
});
|
|
99
|
+
return result.status === 0 ? String(result.stdout || '') : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function previousSkillContent(root, skillPath) {
|
|
103
|
+
for (const ref of ['origin/master', 'origin/main', 'master', 'main', 'HEAD']) {
|
|
104
|
+
const content = gitOutput(root, ['show', `${ref}:${skillPath}`]);
|
|
105
|
+
if (content !== null) return { ref, content };
|
|
106
|
+
}
|
|
107
|
+
return { ref: null, content: '' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildJudgePrompt(skillPath, previousRef) {
|
|
111
|
+
const comparison = previousRef
|
|
112
|
+
? `previous-skill.md is the version from ${previousRef}. Reject regressions even when the current file is acceptable in isolation.`
|
|
113
|
+
: 'There is no previous version. Judge whether the new skill is ready to guide an agent safely and repeatably.';
|
|
114
|
+
return `You are reviewing agent instructions in current-skill.md.
|
|
115
|
+
${comparison}
|
|
116
|
+
|
|
117
|
+
Score each rubric from 1 to 5:
|
|
118
|
+
- instruction_clarity: an agent can follow the instructions without guessing.
|
|
119
|
+
- trigger_precision: the skill says exactly when it should and should not run.
|
|
120
|
+
- procedural_completeness: the required workflow and closure behavior are present.
|
|
121
|
+
- safety: risky actions, permissions, and failure behavior are explicit.
|
|
122
|
+
- verifiability: success can be checked with concrete evidence.
|
|
123
|
+
|
|
124
|
+
Set passed to true only when every score is at least 4 and the current version has no regression from the previous version. Do not edit any files. Return exactly one JSON object and no markdown:
|
|
125
|
+
{"passed":true,"rubric_scores":{"instruction_clarity":4,"trigger_precision":4,"procedural_completeness":4,"safety":4,"verifiability":4},"summary":"one short factual sentence"}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseJudgePayload(output) {
|
|
129
|
+
const text = String(output || '').trim();
|
|
130
|
+
const candidates = [text];
|
|
131
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
132
|
+
if (fenced) candidates.push(fenced[1].trim());
|
|
133
|
+
const firstBrace = text.indexOf('{');
|
|
134
|
+
const lastBrace = text.lastIndexOf('}');
|
|
135
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) candidates.push(text.slice(firstBrace, lastBrace + 1));
|
|
136
|
+
|
|
137
|
+
let payload = null;
|
|
138
|
+
for (const candidate of candidates) {
|
|
139
|
+
try {
|
|
140
|
+
payload = JSON.parse(candidate);
|
|
141
|
+
break;
|
|
142
|
+
} catch {
|
|
143
|
+
// Try the next extraction form.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
147
|
+
throw new Error('the judge did not return one readable JSON object');
|
|
148
|
+
}
|
|
149
|
+
if (!payload.rubric_scores || typeof payload.rubric_scores !== 'object' || Array.isArray(payload.rubric_scores)) {
|
|
150
|
+
throw new Error('the judge did not return rubric scores');
|
|
151
|
+
}
|
|
152
|
+
const rubricScores = {};
|
|
153
|
+
for (const key of RUBRIC_KEYS) {
|
|
154
|
+
const score = Number(payload.rubric_scores[key]);
|
|
155
|
+
if (!Number.isFinite(score) || score < 1 || score > 5) {
|
|
156
|
+
throw new Error(`the judge returned an invalid ${key} score`);
|
|
157
|
+
}
|
|
158
|
+
rubricScores[key] = score;
|
|
159
|
+
}
|
|
160
|
+
const passed = payload.passed === true && Object.values(rubricScores).every((score) => score >= 4);
|
|
161
|
+
return {
|
|
162
|
+
passed,
|
|
163
|
+
rubric_scores: rubricScores,
|
|
164
|
+
summary: String(payload.summary || '').trim().slice(0, 500),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createReviewDirectory(fileSystem = fs) {
|
|
169
|
+
const directory = fileSystem.mkdtempSync(path.join(os.tmpdir(), REVIEW_DIRECTORY_PREFIX));
|
|
170
|
+
if (/(eval|test)/i.test(path.basename(directory))) {
|
|
171
|
+
fileSystem.rmSync(directory, { recursive: true, force: true });
|
|
172
|
+
throw new Error('could not create a neutral review workspace');
|
|
173
|
+
}
|
|
174
|
+
return directory;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function chooseJudge(requested, workerModel, directory, engineFactory = getEngineAdapter) {
|
|
178
|
+
const workerFamily = identityFamily(workerModel);
|
|
179
|
+
const candidates = requested ? [requested] : JUDGE_ORDER;
|
|
180
|
+
const unavailable = [];
|
|
181
|
+
for (const name of candidates) {
|
|
182
|
+
let adapter;
|
|
183
|
+
try {
|
|
184
|
+
adapter = engineFactory(name);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
unavailable.push(`${name}: ${error.message || error}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (identityFamily(adapter.name) === workerFamily) {
|
|
190
|
+
unavailable.push(`${adapter.name}: matches worker model`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const status = adapter.available(directory);
|
|
194
|
+
if (!status.available) {
|
|
195
|
+
unavailable.push(`${adapter.name}: ${status.reason}`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
return adapter;
|
|
199
|
+
}
|
|
200
|
+
const detail = unavailable.length ? ` (${unavailable.join('; ')})` : '';
|
|
201
|
+
throw new Error(`no independent judge engine is available${detail}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function printHelp() {
|
|
205
|
+
console.log('Usage: atris skill eval <skill-path> [--judge <engine>] [--worker-model <id>] [--timeout <seconds>] [--json]');
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log('Runs an independent rubric review and appends its receipt to .atris/state/scorecards.jsonl.');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function skillEvalCommand(args = [], dependencies = {}) {
|
|
211
|
+
const options = parseArgs(args);
|
|
212
|
+
if (options.help) {
|
|
213
|
+
printHelp();
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
const root = path.resolve(dependencies.root || process.cwd());
|
|
217
|
+
let directory = null;
|
|
218
|
+
try {
|
|
219
|
+
const skill = resolveSkillPath(root, options.skillPath);
|
|
220
|
+
const previous = previousSkillContent(root, skill.relative);
|
|
221
|
+
let current = '';
|
|
222
|
+
try {
|
|
223
|
+
current = fs.readFileSync(skill.absolute, 'utf8');
|
|
224
|
+
} catch {
|
|
225
|
+
if (!previous.ref) throw new Error(`could not read ${skill.relative}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
directory = createReviewDirectory(dependencies.fs || fs);
|
|
229
|
+
fs.writeFileSync(path.join(directory, 'current-skill.md'), current || '[skill deleted]\n', 'utf8');
|
|
230
|
+
fs.writeFileSync(path.join(directory, 'previous-skill.md'), previous.content || '[no previous skill]\n', 'utf8');
|
|
231
|
+
const workerModel = options.workerModel || workerModelFromEnvironment(dependencies.env || process.env);
|
|
232
|
+
const judge = chooseJudge(
|
|
233
|
+
options.judge,
|
|
234
|
+
workerModel,
|
|
235
|
+
directory,
|
|
236
|
+
dependencies.getEngineAdapter || getEngineAdapter,
|
|
237
|
+
);
|
|
238
|
+
const result = judge.run(buildJudgePrompt(skill.relative, previous.ref), directory, options.timeoutMs);
|
|
239
|
+
if (!result || result.status !== 0) {
|
|
240
|
+
const detail = String(result && (result.stderr || result.stdout) || '').trim().slice(0, 400);
|
|
241
|
+
throw new Error(`the ${judge.name} judge failed${detail ? `: ${detail}` : ''}`);
|
|
242
|
+
}
|
|
243
|
+
const verdict = parseJudgePayload(result.stdout);
|
|
244
|
+
const changed = changedSkillFiles(root).find((entry) => entry.path === skill.relative);
|
|
245
|
+
const receiptAt = new Date(Math.max(
|
|
246
|
+
Date.now(),
|
|
247
|
+
Number(changed && changed.changed_at_ms || 0) + 1,
|
|
248
|
+
)).toISOString();
|
|
249
|
+
const receipt = {
|
|
250
|
+
schema: SKILL_EVAL_SCHEMA,
|
|
251
|
+
ts: receiptAt,
|
|
252
|
+
source: 'skill_eval',
|
|
253
|
+
skill_path: skill.relative,
|
|
254
|
+
skill_changed_at: changed ? changed.changed_at : null,
|
|
255
|
+
worker_model: workerModel,
|
|
256
|
+
judge_identity: judge.name,
|
|
257
|
+
passed: verdict.passed,
|
|
258
|
+
rubric_scores: verdict.rubric_scores,
|
|
259
|
+
summary: verdict.summary || null,
|
|
260
|
+
};
|
|
261
|
+
const receiptFile = appendSkillEvalReceipt(root, receipt);
|
|
262
|
+
const relativeReceipt = path.relative(root, receiptFile).replace(/\\/g, '/');
|
|
263
|
+
if (options.json) {
|
|
264
|
+
console.log(JSON.stringify({ ok: verdict.passed, receipt, receipt_file: relativeReceipt }));
|
|
265
|
+
} else if (verdict.passed) {
|
|
266
|
+
console.log(`skill eval passed for ${skill.relative}; receipt written to ${relativeReceipt}.`);
|
|
267
|
+
} else {
|
|
268
|
+
console.error(`skill eval failed for ${skill.relative}; the failing receipt was written to ${relativeReceipt}.`);
|
|
269
|
+
}
|
|
270
|
+
return verdict.passed ? 0 : 1;
|
|
271
|
+
} catch (error) {
|
|
272
|
+
const message = String(error && error.message ? error.message : error);
|
|
273
|
+
if (options.json) console.log(JSON.stringify({ ok: false, error: message }));
|
|
274
|
+
else console.error(`skill eval could not run: ${message}.`);
|
|
275
|
+
return 2;
|
|
276
|
+
} finally {
|
|
277
|
+
if (directory) {
|
|
278
|
+
try {
|
|
279
|
+
(dependencies.fs || fs).rmSync(directory, { recursive: true, force: true });
|
|
280
|
+
} catch {
|
|
281
|
+
// The result is already recorded; temporary cleanup is best effort.
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
skillEvalCommand,
|
|
289
|
+
};
|
package/commands/skill.js
CHANGED
|
@@ -440,9 +440,36 @@ function generateTags(folderName, description) {
|
|
|
440
440
|
|
|
441
441
|
// --- Subcommand Handlers ---
|
|
442
442
|
|
|
443
|
-
function
|
|
443
|
+
function skillColor(text, code) {
|
|
444
|
+
if (process.env.NO_COLOR || !process.stdout.isTTY) return String(text);
|
|
445
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function skillList(...flags) {
|
|
449
|
+
const asJson = flags.includes('--json') || process.argv.includes('--json');
|
|
444
450
|
const skills = findReadableSkills();
|
|
445
451
|
|
|
452
|
+
if (asJson) {
|
|
453
|
+
const rows = skills.map((skill) => {
|
|
454
|
+
const checks = runAuditChecks(skill);
|
|
455
|
+
const passing = checks.filter(c => c.pass).length;
|
|
456
|
+
const total = checks.length;
|
|
457
|
+
return {
|
|
458
|
+
name: skill.folder,
|
|
459
|
+
version: (skill.frontmatter || {}).version || null,
|
|
460
|
+
checks_passing: passing,
|
|
461
|
+
checks_total: total,
|
|
462
|
+
status: getStatus(checks),
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
console.log(JSON.stringify({
|
|
466
|
+
ok: true,
|
|
467
|
+
count: rows.length,
|
|
468
|
+
skills: rows,
|
|
469
|
+
}, null, 2));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
446
473
|
if (skills.length === 0) {
|
|
447
474
|
console.log('No skills found in local or bundled Atris skill roots.');
|
|
448
475
|
return;
|
|
@@ -459,9 +486,9 @@ function skillList() {
|
|
|
459
486
|
const total = checks.length;
|
|
460
487
|
const status = getStatus(checks);
|
|
461
488
|
const version = (skill.frontmatter || {}).version || '-';
|
|
462
|
-
const statusIcon = status === 'PASS' ? '
|
|
463
|
-
: status === 'WARN' ? '
|
|
464
|
-
: '
|
|
489
|
+
const statusIcon = status === 'PASS' ? skillColor('PASS', '32')
|
|
490
|
+
: status === 'WARN' ? skillColor('WARN', '33')
|
|
491
|
+
: skillColor('FAIL', '31');
|
|
465
492
|
|
|
466
493
|
if (status !== 'PASS') needsAttention++;
|
|
467
494
|
|
|
@@ -789,6 +816,10 @@ function skillCreate(nameArg, ...flags) {
|
|
|
789
816
|
// --- LINK subcommand (system-level symlink for existing skills) ---
|
|
790
817
|
|
|
791
818
|
function skillLink(name, ...flags) {
|
|
819
|
+
if (name === '--help' || name === '-h' || name === 'help' || flags.some((f) => f === '--help' || f === '-h' || f === 'help')) {
|
|
820
|
+
console.error('Usage: atris skill link [name|--all]');
|
|
821
|
+
process.exit(2);
|
|
822
|
+
}
|
|
792
823
|
const isAll = name === '--all';
|
|
793
824
|
|
|
794
825
|
const skillsDir = path.join(process.cwd(), 'atris', 'skills');
|
|
@@ -934,14 +965,32 @@ function skillDelete(name) {
|
|
|
934
965
|
// --- Main Dispatcher ---
|
|
935
966
|
|
|
936
967
|
function skillCommand(subcommand, ...args) {
|
|
968
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help'
|
|
969
|
+
|| args.some((a) => a === '--help' || a === '-h' || a === 'help')) {
|
|
970
|
+
console.log('');
|
|
971
|
+
console.log('Usage: atris skill <subcommand> [name]');
|
|
972
|
+
console.log('');
|
|
973
|
+
console.log('Subcommands:');
|
|
974
|
+
console.log(' create <name> Scaffold a new skill with SKILL.md template');
|
|
975
|
+
console.log(' delete <name> Remove a skill and all its symlinks');
|
|
976
|
+
console.log(' link [name|--all] Symlink skills to ~/.claude/skills/ (system-level)');
|
|
977
|
+
console.log(' list [--json] Show all skills with compliance status');
|
|
978
|
+
console.log(' audit [name|--all] Validate skill against Anthropic guide');
|
|
979
|
+
console.log(' fix [name|--all] Auto-fix common compliance issues');
|
|
980
|
+
console.log(' eval <skill-path> Run an independent rubric review and write its receipt');
|
|
981
|
+
console.log('');
|
|
982
|
+
process.exit(0);
|
|
983
|
+
}
|
|
937
984
|
switch (subcommand) {
|
|
938
985
|
case 'list':
|
|
939
986
|
case 'ls':
|
|
940
|
-
return skillList();
|
|
987
|
+
return skillList(...args);
|
|
941
988
|
case 'audit':
|
|
942
989
|
return skillAudit(args[0] || '--all');
|
|
943
990
|
case 'fix':
|
|
944
991
|
return skillFix(args[0] || '--all');
|
|
992
|
+
case 'eval':
|
|
993
|
+
return require('./skill-eval').skillEvalCommand(args);
|
|
945
994
|
case 'create':
|
|
946
995
|
case 'new':
|
|
947
996
|
return skillCreate(args[0], ...args.slice(1));
|
|
@@ -959,9 +1008,10 @@ function skillCommand(subcommand, ...args) {
|
|
|
959
1008
|
console.log(' create <name> Scaffold a new skill with SKILL.md template');
|
|
960
1009
|
console.log(' delete <name> Remove a skill and all its symlinks');
|
|
961
1010
|
console.log(' link [name|--all] Symlink skills to ~/.claude/skills/ (system-level)');
|
|
962
|
-
console.log(' list
|
|
1011
|
+
console.log(' list [--json] Show all skills with compliance status');
|
|
963
1012
|
console.log(' audit [name|--all] Validate skill against Anthropic guide');
|
|
964
1013
|
console.log(' fix [name|--all] Auto-fix common compliance issues');
|
|
1014
|
+
console.log(' eval <skill-path> Run an independent rubric review and write its receipt');
|
|
965
1015
|
console.log('');
|
|
966
1016
|
console.log('Create flags:');
|
|
967
1017
|
console.log(' --integration Use integration template (bootstrap + API)');
|