@shinaps/skills 0.43.0 → 0.45.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/README.md +1 -1
- package/dist/bin/skills.js +10 -7
- package/dist/lib/auto-update.js +4 -4
- package/dist/lib/config-validation.js +1 -1
- package/dist/lib/install.js +6 -5
- package/dist/lib/linear-config.js +1 -1
- package/dist/lib/lint-plan.js +173 -0
- package/dist/lib/lint-rules.js +52 -0
- package/dist/lib/lint.js +233 -0
- package/dist/lib/lock.js +1 -1
- package/dist/lib/managed-config.js +1 -1
- package/dist/lib/managed-session.js +43 -20
- package/dist/lib/managed-setup.js +4 -4
- package/dist/lib/managed-snapshot.js +175 -0
- package/dist/lib/managed.js +47 -11
- package/dist/lib/transaction.js +2 -2
- package/docs/development.md +1 -1
- package/docs/install.md +3 -3
- package/docs/lint.md +71 -0
- package/package.json +9 -5
- package/plugins/shinaps-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/shinaps-claude/managed-agents/skills/dev-managed-cc/references/launch.md +4 -0
- package/plugins/shinaps-claude/references/linear/configuration.md +1 -1
- package/plugins/shinaps-claude/references/lint.md +34 -0
- package/plugins/shinaps-claude/references/managed/configuration.md +1 -1
- package/plugins/shinaps-claude/references/managed/configuration.schema.json +1 -1
- package/plugins/shinaps-claude/references/managed/execute.md +26 -2
- package/plugins/shinaps-claude/references/rule-writing.md +5 -1
- package/plugins/shinaps-claude/references/rules.md +17 -4
- package/plugins/shinaps-claude/skills/explore/SKILL.md +1 -1
- package/plugins/shinaps-claude/skills/extract-rules/SKILL.md +2 -2
- package/plugins/shinaps-claude/skills/managed-execute/SKILL.md +8 -2
- package/plugins/shinaps-claude/skills/refine/SKILL.md +7 -3
- package/plugins/shinaps-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/shinaps-codex/managed-agents/skills/dev-managed-cc/references/launch.md +4 -0
- package/plugins/shinaps-codex/references/linear/configuration.md +1 -1
- package/plugins/shinaps-codex/references/lint.md +34 -0
- package/plugins/shinaps-codex/references/managed/configuration.md +1 -1
- package/plugins/shinaps-codex/references/managed/configuration.schema.json +1 -1
- package/plugins/shinaps-codex/references/managed/execute.md +26 -2
- package/plugins/shinaps-codex/references/rule-writing.md +5 -1
- package/plugins/shinaps-codex/references/rules.md +17 -4
- package/plugins/shinaps-codex/skills/extract-rules/SKILL.md +1 -1
- package/plugins/shinaps-codex/skills/managed-execute/SKILL.md +8 -2
- package/plugins/shinaps-codex/skills/refine/SKILL.md +2 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, readdir, rename, rm, stat } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
|
-
import { readJson, readJsonIfExists, writeFileAtomic } from
|
|
5
|
-
import { withLock } from
|
|
6
|
-
import { filesBeta, managedBeta } from
|
|
4
|
+
import { readJson, readJsonIfExists, writeFileAtomic } from "./fs.js";
|
|
5
|
+
import { withLock } from "./lock.js";
|
|
6
|
+
import { filesBeta, managedBeta } from "./managed-ant.js";
|
|
7
|
+
import { importSnapshot } from "./managed-snapshot.js";
|
|
7
8
|
export const staffings = ['LITE', 'BASIC', 'ULTRA'];
|
|
8
9
|
export const permissions = ['commit', 'push', 'draft_pr'];
|
|
9
10
|
const leadKey = './agents/shinaps-dev-managed-cc.md';
|
|
@@ -64,27 +65,28 @@ export async function runSession(account, request, options) {
|
|
|
64
65
|
};
|
|
65
66
|
// 作成より前に記録する。作成後に記録できずに終わっても、次の実行は止まり、重複して作らない。
|
|
66
67
|
await writeFileAtomic(recordPath, JSON.stringify(record, null, 2) + '\n');
|
|
67
|
-
const resources =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
const resources = request.snapshot
|
|
69
|
+
? []
|
|
70
|
+
: [
|
|
71
|
+
{
|
|
72
|
+
type: 'github_repository',
|
|
73
|
+
url: request.repository.url,
|
|
74
|
+
mount_path: request.repository.mount_path,
|
|
75
|
+
...(options.githubToken ? { authorization_token: options.githubToken } : {}),
|
|
76
|
+
checkout: { type: 'commit', sha: request.base_sha },
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
// upload は記録の後に行う。記録の前だと、途中で止まった実行の再実行のたびに Files が増える。
|
|
80
|
+
const upload = async (file, mountPath) => {
|
|
79
81
|
try {
|
|
80
|
-
fileId = await readId(ant({
|
|
82
|
+
const fileId = await readId(ant({
|
|
81
83
|
// セッションの Files と同じ beta の Files API に上げる。パスは絶対パスにする (ant は `-` を
|
|
82
84
|
// stdin と解釈する)。
|
|
83
85
|
argv: [
|
|
84
86
|
'beta:files',
|
|
85
87
|
'upload',
|
|
86
88
|
'--file',
|
|
87
|
-
resolve(
|
|
89
|
+
resolve(file),
|
|
88
90
|
...filesBeta,
|
|
89
91
|
'--transform',
|
|
90
92
|
'id',
|
|
@@ -92,14 +94,18 @@ export async function runSession(account, request, options) {
|
|
|
92
94
|
],
|
|
93
95
|
apiKey: account.apiKey,
|
|
94
96
|
}), 'ant files upload');
|
|
97
|
+
resources.push({ type: 'file', file_id: fileId, mount_path: mountPath });
|
|
95
98
|
}
|
|
96
99
|
catch (error) {
|
|
97
100
|
// セッションはまだ作っていないので、記録を残すと同じ依頼の再実行が不要に止まる。
|
|
98
101
|
await rm(recordPath, { force: true });
|
|
99
102
|
throw error;
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
};
|
|
105
|
+
if (request.snapshot)
|
|
106
|
+
await upload(request.snapshot.bundle, '/source.bundle');
|
|
107
|
+
if (options.claudeMdPath)
|
|
108
|
+
await upload(options.claudeMdPath, '/CLAUDE.md');
|
|
103
109
|
const body = {
|
|
104
110
|
agent,
|
|
105
111
|
environment_id: environment,
|
|
@@ -140,6 +146,16 @@ function initialMessage(request) {
|
|
|
140
146
|
`必須検証(任意): ${request.verify ?? '指定なし。リポジトリの規約に従う'}`,
|
|
141
147
|
`author(任意): ${request.author ?? '指定なし'}`,
|
|
142
148
|
...(request.claude_md === null ? [] : ['利用者 CLAUDE.md: /mnt/session/uploads/CLAUDE.md']),
|
|
149
|
+
...(request.snapshot
|
|
150
|
+
? [
|
|
151
|
+
'ローカルコードの受け渡し: GitHubのcheckoutの代わりに /mnt/session/uploads/source.bundle を使用する。',
|
|
152
|
+
`作業前に git clone /mnt/session/uploads/source.bundle ${JSON.stringify(request.repository.mount_path)} を実行する。`,
|
|
153
|
+
`比較元は送信時点のスナップショット ${request.snapshot.id}。このSHAからheadブランチを作り作業する。`,
|
|
154
|
+
'このスナップショットには元のGit履歴・remote・資格情報は含まれない。pushとPRは行わない。',
|
|
155
|
+
`完了時はlaunch.mdの一時indexによる出力手順に従い、新規ファイルを含む ${request.snapshot.id} からの全差分を /mnt/session/outputs/changes.patch に書く。`,
|
|
156
|
+
'変更内容・検証結果・未完了事項を /mnt/session/outputs/report.md に書き、ローカルで再開できるようにする。',
|
|
157
|
+
]
|
|
158
|
+
: []),
|
|
143
159
|
].join('\n');
|
|
144
160
|
}
|
|
145
161
|
/**
|
|
@@ -152,10 +168,17 @@ async function requireRecordedSession(account, sessionId) {
|
|
|
152
168
|
for (const name of names.filter((entry) => entry.endsWith('.json'))) {
|
|
153
169
|
const record = await readJson(join(directory, name));
|
|
154
170
|
if (record.session_id === sessionId)
|
|
155
|
-
return;
|
|
171
|
+
return record;
|
|
156
172
|
}
|
|
157
173
|
throw new Error(`Session ${sessionId} was not created by managed run for account ${account.name}.`);
|
|
158
174
|
}
|
|
175
|
+
/** 回収済みのローカル snapshot のセッションを、APIキーを使わずに新しいディレクトリへ復元する。 */
|
|
176
|
+
export async function importSession(account, sessionId, destination) {
|
|
177
|
+
const record = await requireRecordedSession(account, sessionId);
|
|
178
|
+
if (!record.request.snapshot)
|
|
179
|
+
throw new Error('This session did not use a local snapshot.');
|
|
180
|
+
return importSnapshot(record.request.snapshot, join(account.dir, 'sessions', sessionId, 'outputs', 'changes.patch'), destination);
|
|
181
|
+
}
|
|
159
182
|
/** セッションの状態と、lead の最後のメッセージ (1 行目が `状態: 完了|質問|未完了`) を返す。 */
|
|
160
183
|
export async function sessionStatus(account, sessionId, ant) {
|
|
161
184
|
await requireRecordedSession(account, sessionId);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { lstat, mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, sep } from 'node:path';
|
|
4
|
-
import { errorCode, readJsonIfExists, readTextIfExists, writeFileAtomic } from
|
|
5
|
-
import { withLock } from
|
|
6
|
-
import { managedTemplatePath } from
|
|
7
|
-
import { managedBeta } from
|
|
4
|
+
import { errorCode, readJsonIfExists, readTextIfExists, writeFileAtomic } from "./fs.js";
|
|
5
|
+
import { withLock } from "./lock.js";
|
|
6
|
+
import { managedTemplatePath } from "./layout.js";
|
|
7
|
+
import { managedBeta } from "./managed-ant.js";
|
|
8
8
|
/** 共通定義の 3 ディレクトリ。`definitions/` には、これと `claude-lock.json` 以外を置かない。 */
|
|
9
9
|
const definitionDirs = ['agents', 'environments', 'skills'];
|
|
10
10
|
const lockName = 'claude-lock.json';
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { copyFile, lstat, mkdir, mkdtemp, readFile, readlink, realpath, rename, rm, symlink, chmod, } from 'node:fs/promises';
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { readJson, writeFileAtomic } from "./fs.js";
|
|
6
|
+
const execute = promisify(execFile);
|
|
7
|
+
// 呼び出し元の index の上書き・hooks・filter・署名を snapshot に持ち込まない。
|
|
8
|
+
async function git(cwd, args, configuration = 'snapshot') {
|
|
9
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_') || (configuration === 'source' && key.startsWith('GIT_CONFIG_'))));
|
|
10
|
+
const { stdout } = await execute('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'core.autocrlf=false', ...args], {
|
|
11
|
+
cwd,
|
|
12
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
13
|
+
env: {
|
|
14
|
+
...env,
|
|
15
|
+
...(configuration === 'snapshot'
|
|
16
|
+
? { GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null' }
|
|
17
|
+
: {}),
|
|
18
|
+
GIT_AUTHOR_NAME: 'Managed Snapshot',
|
|
19
|
+
GIT_AUTHOR_EMAIL: 'snapshot@example.invalid',
|
|
20
|
+
GIT_COMMITTER_NAME: 'Managed Snapshot',
|
|
21
|
+
GIT_COMMITTER_EMAIL: 'snapshot@example.invalid',
|
|
22
|
+
GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
|
|
23
|
+
GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return stdout;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 元の index を変えずに、現在のソースのファイルを1コミットだけの独立したリポジトリへ固定する。
|
|
30
|
+
* @param source Git の checkout のルート。未コミットの変更と、無視されていない新規ファイルを含めて送る。
|
|
31
|
+
* @param root checkout の外にある、snapshot の非公開の保存先。
|
|
32
|
+
*/
|
|
33
|
+
export async function prepareSnapshot(source, root) {
|
|
34
|
+
source = await realpath(source);
|
|
35
|
+
const top = (await git(source, ['rev-parse', '--show-toplevel'], 'source')).trim();
|
|
36
|
+
if ((await realpath(top)) !== source)
|
|
37
|
+
throw new Error('Snapshot source must be the Git checkout root.');
|
|
38
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
39
|
+
root = await realpath(root);
|
|
40
|
+
if (root === source || root.startsWith(source + sep))
|
|
41
|
+
throw new Error('Snapshot storage must be outside the source checkout.');
|
|
42
|
+
const temporary = await mkdtemp(join(root, '.snapshot-'));
|
|
43
|
+
try {
|
|
44
|
+
const repository = join(temporary, 'repository');
|
|
45
|
+
await mkdir(repository);
|
|
46
|
+
// 方式を固定する。git の既定が変わっても、readSnapshot が受け付ける 40 桁の id にする。
|
|
47
|
+
await git(repository, ['init', '--object-format=sha1', '--initial-branch=snapshot']);
|
|
48
|
+
const candidates = [
|
|
49
|
+
...new Set((await git(source, ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], 'source'))
|
|
50
|
+
.split('\0')
|
|
51
|
+
.filter(Boolean)),
|
|
52
|
+
].sort();
|
|
53
|
+
const files = [];
|
|
54
|
+
for (const name of candidates) {
|
|
55
|
+
// ローカルの運用状態・資格情報・Git の内部・生成された依存物は送らない。
|
|
56
|
+
const parts = name.split('/');
|
|
57
|
+
if (parts.some((part) => ['.git', '.shinaps', 'node_modules', '.local'].includes(part)))
|
|
58
|
+
continue;
|
|
59
|
+
if (parts.some((part) => /^\.env(?:\.|$)/.test(part) && !/\.(example|sample|template)$/.test(part)) ||
|
|
60
|
+
/\.(pem|p12|pfx|key)$/i.test(name)) {
|
|
61
|
+
throw new Error(`Remove credential file from the snapshot source before sending: ${name}`);
|
|
62
|
+
}
|
|
63
|
+
const input = join(source, name);
|
|
64
|
+
let info;
|
|
65
|
+
try {
|
|
66
|
+
info = await lstat(input);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code === 'ENOENT')
|
|
70
|
+
continue;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
if (!info.isFile() && !info.isSymbolicLink())
|
|
74
|
+
throw new Error(`Snapshot does not support directories or submodules as files: ${name}`);
|
|
75
|
+
// 親ディレクトリも確かめる。途中のディレクトリを差し替えると、checkout 外のファイルを読めてしまう。
|
|
76
|
+
const parent = await realpath(dirname(input));
|
|
77
|
+
if (parent !== source && !parent.startsWith(source + sep))
|
|
78
|
+
throw new Error(`Snapshot path leaves the source checkout: ${name}`);
|
|
79
|
+
const output = join(repository, name);
|
|
80
|
+
await mkdir(dirname(output), { recursive: true });
|
|
81
|
+
if (info.isSymbolicLink()) {
|
|
82
|
+
const target = await readlink(input);
|
|
83
|
+
const destination = resolve(dirname(input), target);
|
|
84
|
+
if (target.startsWith('/') ||
|
|
85
|
+
(destination !== source && !destination.startsWith(source + sep)))
|
|
86
|
+
throw new Error(`Snapshot symlink leaves the source checkout: ${name}`);
|
|
87
|
+
await symlink(target, output);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await copyFile(input, output);
|
|
91
|
+
await chmod(output, info.mode & 0o111 ? 0o755 : 0o644);
|
|
92
|
+
}
|
|
93
|
+
files.push(name);
|
|
94
|
+
}
|
|
95
|
+
await git(repository, ['add', '--all', '--force']);
|
|
96
|
+
await git(repository, [
|
|
97
|
+
'-c',
|
|
98
|
+
'commit.gpgsign=false',
|
|
99
|
+
'commit',
|
|
100
|
+
'--allow-empty',
|
|
101
|
+
'-m',
|
|
102
|
+
'Local source snapshot',
|
|
103
|
+
]);
|
|
104
|
+
const id = (await git(repository, ['rev-parse', 'HEAD'])).trim();
|
|
105
|
+
const directory = join(root, id);
|
|
106
|
+
await git(repository, ['bundle', 'create', join(temporary, 'source.bundle'), 'HEAD']);
|
|
107
|
+
const snapshot = {
|
|
108
|
+
id,
|
|
109
|
+
source,
|
|
110
|
+
directory,
|
|
111
|
+
bundle: join(directory, 'source.bundle'),
|
|
112
|
+
files,
|
|
113
|
+
};
|
|
114
|
+
await writeFileAtomic(join(temporary, 'snapshot.json'), JSON.stringify(snapshot, null, 2) + '\n');
|
|
115
|
+
try {
|
|
116
|
+
await rename(temporary, directory);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (!['EEXIST', 'ENOTEMPTY'].includes(error.code ?? ''))
|
|
120
|
+
throw error;
|
|
121
|
+
// 同じ内容は別の checkout からも作られうるが、比較元としては同じ tree である。
|
|
122
|
+
return readSnapshot(join(directory, 'snapshot.json'));
|
|
123
|
+
}
|
|
124
|
+
return snapshot;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await rm(temporary, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* prepare が書いた manifest だけを受け付ける。id は git の引数と lead への指示文に、bundle は upload に
|
|
132
|
+
* 使うので、別のファイルを渡された場合にそのまま使わない。
|
|
133
|
+
* @param manifest prepare が出力した `snapshot.json` のパス。
|
|
134
|
+
*/
|
|
135
|
+
export async function readSnapshot(manifest) {
|
|
136
|
+
const { id, source, directory, bundle, files } = (await readJson(manifest)) ?? {};
|
|
137
|
+
if (typeof id !== 'string' ||
|
|
138
|
+
!/^[0-9a-f]{40}$/.test(id) ||
|
|
139
|
+
typeof source !== 'string' ||
|
|
140
|
+
typeof directory !== 'string' ||
|
|
141
|
+
!isAbsolute(directory) ||
|
|
142
|
+
bundle !== join(directory, 'source.bundle') ||
|
|
143
|
+
!Array.isArray(files) ||
|
|
144
|
+
!files.every((file) => typeof file === 'string'))
|
|
145
|
+
throw new Error(`Not a snapshot manifest written by managed prepare: ${manifest}`);
|
|
146
|
+
return { id, source, directory, bundle, files };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 比較元と差分を新しいディレクトリに復元する。元の checkout は変更しない。
|
|
150
|
+
* @param snapshot リモートのセッションが使った、保存済みの比較元。
|
|
151
|
+
* @param patch 回収した lead の差分。比較元からの全体の差分。
|
|
152
|
+
* @param destination 復元先の新しいディレクトリ。既に存在してはいけない。
|
|
153
|
+
*/
|
|
154
|
+
export async function importSnapshot(snapshot, patch, destination) {
|
|
155
|
+
destination = resolve(destination);
|
|
156
|
+
await mkdir(destination, { mode: 0o700 });
|
|
157
|
+
try {
|
|
158
|
+
await git(destination, [
|
|
159
|
+
'clone',
|
|
160
|
+
'--no-local',
|
|
161
|
+
'--',
|
|
162
|
+
join(snapshot.directory, 'repository'),
|
|
163
|
+
'.',
|
|
164
|
+
]);
|
|
165
|
+
await git(destination, ['checkout', '--detach', snapshot.id]);
|
|
166
|
+
const content = await readFile(patch);
|
|
167
|
+
if (content.length)
|
|
168
|
+
await git(destination, ['apply', '--index', '--', resolve(patch)]);
|
|
169
|
+
return destination;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
await rm(destination, { recursive: true, force: true });
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
package/dist/lib/managed.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { errorMessage } from "./fs.js";
|
|
5
|
+
import { managedAccountDir, managedRoot, packageRoot } from "./layout.js";
|
|
6
|
+
import { antCommand } from "./managed-ant.js";
|
|
7
|
+
import { validateManagedConfig } from "./managed-config.js";
|
|
8
|
+
import { collectOutputs, importSession, permissions, replySession, runSession, sessionStatus, staffings, } from "./managed-session.js";
|
|
9
|
+
import { setupAccount } from "./managed-setup.js";
|
|
10
|
+
import { prepareSnapshot, readSnapshot } from "./managed-snapshot.js";
|
|
9
11
|
const managedUsage = [
|
|
10
12
|
'Usage: shinaps-skills managed setup --account <name> [--account <name> ...] [--apply]',
|
|
11
|
-
' shinaps-skills managed run <managed.json> --account <name> --base <branch> --base-sha <sha> --head <branch> --staffing <LITE|BASIC|ULTRA> --allow <none|commit,push,draft_pr> --budget-cents <n> --task-file <file> [--verify <command>] [--author "<name> <email>"] [--title <title>] [--claude-md <file>]',
|
|
13
|
+
' shinaps-skills managed run <managed.json> --account <name> --base <branch> (--base-sha <sha> | --snapshot <manifest>) --head <branch> --staffing <LITE|BASIC|ULTRA> --allow <none|commit,push,draft_pr> --budget-cents <n> --task-file <file> [--verify <command>] [--author "<name> <email>"] [--title <title>] [--claude-md <file>]',
|
|
12
14
|
' shinaps-skills managed status --account <name> --session <id>',
|
|
13
15
|
' shinaps-skills managed reply --account <name> --session <id> --message-file <file>',
|
|
14
16
|
' shinaps-skills managed collect --account <name> --session <id>',
|
|
17
|
+
' shinaps-skills managed prepare --source <local-checkout-root>',
|
|
18
|
+
' shinaps-skills managed import --account <name> --session <id> --destination <new-directory>',
|
|
15
19
|
'API keys: --api-key <name>=<key>, or SHINAPS_MANAGED_API_KEY_<NAME> (prefer the environment variable; arguments are visible to other processes).',
|
|
16
20
|
'GitHub token for the mounted repository: SHINAPS_MANAGED_GITHUB_TOKEN.',
|
|
21
|
+
'Local source: prepare lists the frozen files and manifest; pass --snapshot <manifest> to run instead of --base-sha. Local snapshots do not push or create PRs.',
|
|
17
22
|
].join('\n');
|
|
18
|
-
const accountName = /^[a-z0-9]
|
|
23
|
+
const accountName = /^[a-z0-9][a-z0-9-]*$/;
|
|
19
24
|
const githubTokenVariable = 'SHINAPS_MANAGED_GITHUB_TOKEN';
|
|
20
25
|
const valueFlags = new Set([
|
|
21
26
|
'account',
|
|
@@ -33,12 +38,15 @@ const valueFlags = new Set([
|
|
|
33
38
|
'claude-md',
|
|
34
39
|
'session',
|
|
35
40
|
'message-file',
|
|
41
|
+
'source',
|
|
42
|
+
'snapshot',
|
|
43
|
+
'destination',
|
|
36
44
|
]);
|
|
37
45
|
const repeatableFlags = new Set(['account', 'api-key']);
|
|
38
46
|
/** アカウント名ごとのキーの環境変数名。名前に `_` を許さないので、名前と一対一に対応する。 */
|
|
39
47
|
const apiKeyVariable = (account) => `SHINAPS_MANAGED_API_KEY_${account.toUpperCase().replaceAll('-', '_')}`;
|
|
40
48
|
/**
|
|
41
|
-
* `shinaps-skills managed <setup|run|status|reply|collect>` を実行する。出力とエラー文から、渡された
|
|
49
|
+
* `shinaps-skills managed <setup|run|status|reply|collect|prepare|import>` を実行する。出力とエラー文から、渡された
|
|
42
50
|
* APIキーと GitHub token の値を伏字にする。
|
|
43
51
|
*/
|
|
44
52
|
export async function managed(args, options = {}) {
|
|
@@ -74,6 +82,25 @@ async function dispatch(args, context) {
|
|
|
74
82
|
const home = context.home ?? homedir();
|
|
75
83
|
const ant = context.ant ?? antCommand(context.env);
|
|
76
84
|
const accounts = flags.get('account') ?? [];
|
|
85
|
+
if (command === 'prepare') {
|
|
86
|
+
if (apply || positional.length || accounts.length)
|
|
87
|
+
throw usageError();
|
|
88
|
+
const snapshot = await prepareSnapshot(required(flags, 'source'), join(managedRoot(home), 'snapshots'));
|
|
89
|
+
context.log(JSON.stringify({ ...snapshot, manifest: join(snapshot.directory, 'snapshot.json') }, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (command === 'import') {
|
|
93
|
+
if (apply || positional.length || accounts.length !== 1 || !accountName.test(accounts[0]))
|
|
94
|
+
throw usageError();
|
|
95
|
+
const account = { name: accounts[0], dir: managedAccountDir(managedRoot(home), accounts[0]) };
|
|
96
|
+
const sessionId = required(flags, 'session');
|
|
97
|
+
context.log(JSON.stringify({
|
|
98
|
+
account: account.name,
|
|
99
|
+
session_id: sessionId,
|
|
100
|
+
directory: await importSession(account, sessionId, required(flags, 'destination')),
|
|
101
|
+
}, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
77
104
|
if (command === 'setup') {
|
|
78
105
|
if (positional.length || !accounts.length)
|
|
79
106
|
throw usageError();
|
|
@@ -164,12 +191,19 @@ async function run(configPath, account, flags, context, ant) {
|
|
|
164
191
|
}
|
|
165
192
|
const optional = (flag) => flags.get(flag)?.[0] ?? null;
|
|
166
193
|
const claudeMdPath = optional('claude-md');
|
|
194
|
+
const snapshotPath = optional('snapshot');
|
|
195
|
+
if (snapshotPath && flags.has('base-sha'))
|
|
196
|
+
throw new Error('Use either --base-sha or --snapshot.');
|
|
197
|
+
const snapshot = snapshotPath ? await readSnapshot(snapshotPath) : undefined;
|
|
198
|
+
if (snapshot && (allow.includes('push') || allow.includes('draft_pr'))) {
|
|
199
|
+
throw new Error('Local snapshots support local result recovery; push and draft_pr are not available.');
|
|
200
|
+
}
|
|
167
201
|
const request = {
|
|
168
202
|
account: account.name,
|
|
169
203
|
repository: config.repository,
|
|
170
204
|
vault_id: settings.vault_id,
|
|
171
205
|
base: required(flags, 'base'),
|
|
172
|
-
base_sha: required(flags, 'base-sha'),
|
|
206
|
+
base_sha: snapshot?.id ?? required(flags, 'base-sha'),
|
|
173
207
|
head: required(flags, 'head'),
|
|
174
208
|
staffing: staffing,
|
|
175
209
|
allow,
|
|
@@ -179,6 +213,7 @@ async function run(configPath, account, flags, context, ant) {
|
|
|
179
213
|
author: optional('author'),
|
|
180
214
|
title: optional('title'),
|
|
181
215
|
claude_md: claudeMdPath === null ? null : await readFile(claudeMdPath, 'utf8'),
|
|
216
|
+
...(snapshot ? { snapshot } : {}),
|
|
182
217
|
};
|
|
183
218
|
const sessionId = await runSession(account, request, {
|
|
184
219
|
ant,
|
|
@@ -188,6 +223,7 @@ async function run(configPath, account, flags, context, ant) {
|
|
|
188
223
|
now: context.now ?? (() => new Date()),
|
|
189
224
|
});
|
|
190
225
|
context.log(`Observe: shinaps-skills managed status --account ${account.name} --session ${sessionId}`);
|
|
226
|
+
context.log(JSON.stringify({ account: account.name, session_id: sessionId }));
|
|
191
227
|
}
|
|
192
228
|
function resolveAccounts(names, flags, env, home) {
|
|
193
229
|
const keys = new Map();
|
|
@@ -200,7 +236,7 @@ function resolveAccounts(names, flags, env, home) {
|
|
|
200
236
|
const unique = [...new Set(names)];
|
|
201
237
|
const invalid = unique.filter((name) => !accountName.test(name));
|
|
202
238
|
if (invalid.length) {
|
|
203
|
-
throw new Error(`Invalid account name: ${invalid.join(', ')}. Use lowercase letters, digits and
|
|
239
|
+
throw new Error(`Invalid account name: ${invalid.join(', ')}. Use lowercase letters, digits and hyphens, starting with a letter or digit.`);
|
|
204
240
|
}
|
|
205
241
|
const missing = unique.filter((name) => !(keys.get(name) || env[apiKeyVariable(name)]));
|
|
206
242
|
if (missing.length) {
|
package/dist/lib/transaction.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdir, rm, rename, cp } from 'node:fs/promises';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { managedAgent, marketplaceName, payloadDir, pluginId, statePath } from
|
|
5
|
-
import { errorCode, errorMessage, readJsonIfExists, readTextIfExists, writeFileAtomic, } from
|
|
4
|
+
import { managedAgent, marketplaceName, payloadDir, pluginId, statePath } from "./layout.js";
|
|
5
|
+
import { errorCode, errorMessage, readJsonIfExists, readTextIfExists, writeFileAtomic, } from "./fs.js";
|
|
6
6
|
/** エージェント内容の sha256。state.json と journal の `nextHashes` はこの形式で記録する。 */
|
|
7
7
|
export const digest = (text) => createHash('sha256').update(text).digest('hex');
|
|
8
8
|
/** 配布物を Codex に登録する。install と restore で同じ手順を使う。 */
|
package/docs/development.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- Claude 版: `plugins/shinaps-claude/`、marketplace は `.claude-plugin/marketplace.json`
|
|
8
8
|
- Codex 版: `plugins/shinaps-codex/`、marketplace は `.agents/plugins/marketplace.json`。サブエージェントは `plugins/shinaps-codex/agents/`
|
|
9
9
|
- Claude Managed Agents 版: 両 plugin の `managed-agents/`。plugin と一緒に配布され、npm パッケージの `plugins/` にも含まれる
|
|
10
|
-
- 配布 CLI: ソースは `bin/skills.ts` と `lib/*.ts
|
|
10
|
+
- 配布 CLI: ソースは `bin/skills.ts` と `lib/*.ts`。規約の機械検査(`lint`)は ESLint の Node API を使い、仕組みと同梱ルールの追加手順は [規約の機械検査](lint.md) にある。TypeScript は typescript-eslint が対応する 6 系を実行時の依存に持ち、開発用の `tsc` も同じ版を使う(typescript-eslint が 7 に対応したら上げる)。`npm run build`(`tsc`)で `dist/` に JS を出力し、npm にはそれを配布する。Node.js は `node_modules` 配下の `.ts` に型除去を適用しないため、ソースのままは配布できない。checkout では型除去で直接実行するので、`enum` など型除去で消せない構文は使わない(`tsconfig.json` の `erasableSyntaxOnly`)
|
|
11
11
|
|
|
12
12
|
品質基準とプロジェクト設定の方針は両版で揃え、クライアント固有の実行手順とエージェントの形式は別々に管理する。スキルを追加・変更したら各版への適用可否を判断する。
|
|
13
13
|
|
package/docs/install.md
CHANGED
|
@@ -4,7 +4,7 @@ Claude Code 版の更新と、Codex 版の導入・更新・削除・macOS で
|
|
|
4
4
|
|
|
5
5
|
## 導入・更新・削除
|
|
6
6
|
|
|
7
|
-
Node.js 22 以上と、`plugin marketplace add` / `plugin add` に対応した Codex CLI が必要です。Codex のスキルだけを使う場合、Claude Code のインストールは不要です。
|
|
7
|
+
Node.js 22.13 以上の 22 系または 24 以上と、`plugin marketplace add` / `plugin add` に対応した Codex CLI が必要です。Codex のスキルだけを使う場合、Claude Code のインストールは不要です。
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
npx @shinaps/skills@latest install codex
|
|
@@ -23,10 +23,10 @@ npx @shinaps/skills@latest recover codex
|
|
|
23
23
|
|
|
24
24
|
## 配布 CLI
|
|
25
25
|
|
|
26
|
-
設定の検証(`validate-linear-config`・`validate-managed-config
|
|
26
|
+
設定の検証(`validate-linear-config`・`validate-managed-config`)、Managed Agents の操作(`managed`)、規約の機械検査(`lint`、[規約の機械検査](lint.md))には、配布 CLI `shinaps-skills` を使います。CLI は plugin に含まれないため、利用中のスキルと同じ版を導入します。
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
npm install -g @shinaps/skills@0.
|
|
29
|
+
npm install -g @shinaps/skills@0.45.0
|
|
30
30
|
shinaps-skills --version
|
|
31
31
|
```
|
|
32
32
|
|
package/docs/lint.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 規約の機械検査(`shinaps-skills lint`)
|
|
2
|
+
|
|
3
|
+
Shinaps AIの規約が持つ機械検査(`checks`)を手元で実行し、規約×ファイルの組ごとに、AI照合を省けるかを判定するCLIです。エージェント向けの使い方は plugin の `references/lint.md` にあります。
|
|
4
|
+
|
|
5
|
+
## 前提と保証
|
|
6
|
+
|
|
7
|
+
- プロジェクトの変更は要りません。プロジェクトのlint設定(`eslint.config.*`・`.oxlintrc.json`・`bunfig.toml` など)・`package.json` のscript・`node_modules` のパッケージは読み込みません。ESLintのNode APIを `overrideConfigFile: true` で使い、パーサーとプラグインはこのパッケージの依存から渡します。読むのは対象ファイルと、型情報を使うルールのための `tsconfig.json`・型宣言だけです(実行はしません)。
|
|
8
|
+
- 対象ファイルへ書き込みません(自動修正はしません)。結果はローカルのファイルに書き、サーバーへは送りません。
|
|
9
|
+
- プロジェクトの `eslint-disable` コメントでは検査を抑止しません(`allowInlineConfig: false`)。プロジェクトの都合の例外は、指摘を確認する側で扱います。
|
|
10
|
+
- 組をAI照合から外してよいのは、coverage が `full` の検査が、ESLintの判定でそのファイルに適用され、検査の実行単位が完了し、そのファイルに致命的な報告もruleIdの無い報告も無いときだけです。それ以外の組は `not-run`(または `partial`)として返し、合格にしません。coverage は検査を登録したときの判断で、このCLIは値を読むだけです。
|
|
11
|
+
|
|
12
|
+
## 使い方
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
shinaps-skills lint --checks <checks.json> --root <project> --out <result.json> -- <files...>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- `--checks`・`--out` のパスは、コマンドを実行したディレクトリが基準です。
|
|
19
|
+
- `--checks`: `{ "rules": [{ "id": "<規約id>", "checks": [{ "rule": "...", "options": [...], "files": ["**/*.ts"], "coverage": "full" }] }] }`。形が違えば何も書かずに終了コード1で終わります。
|
|
20
|
+
- `--root`: 対象プロジェクトのルート。ESLintの `cwd` と、結果のパスの基準になります。
|
|
21
|
+
- `<files...>`: 対象ファイル(`--root` からの相対パスか絶対パス)。どの検査も適用されないファイルはESLintに渡しません。渡すファイルが無ければESLintを起動しません(ファイルを省くとESLintはルート配下を全て検査するため)。
|
|
22
|
+
- `--out`: 結果のJSON。
|
|
23
|
+
|
|
24
|
+
結果:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"root": "/path/to/project",
|
|
29
|
+
"units": [{ "id": "syntax-0", "typed": false, "rules": ["@typescript-eslint/method-signature-style"], "fileCount": 3 }],
|
|
30
|
+
"pairs": [
|
|
31
|
+
{ "ruleId": "<規約id>", "file": "src/a.ts", "state": "covered", "findings": [] },
|
|
32
|
+
{ "ruleId": "<規約id>", "file": "src/b.ts", "state": "violation", "findings": [{ "rule": "@typescript-eslint/method-signature-style", "line": 2, "column": 3, "message": "..." }] },
|
|
33
|
+
{ "ruleId": "<規約id>", "file": "tests/c.ts", "state": "not-run", "reason": "...", "findings": [] }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| state | 意味 | 照合 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `covered` | `full` の検査が実行され、違反が無い | 外す |
|
|
41
|
+
| `violation` | `full` の検査が実行され、指摘がある(同じ規約の `partial` の検査の指摘を含む) | 外し、`findings` を指摘にする |
|
|
42
|
+
| `partial` | `partial` の検査だけが実行された | 残し、`findings` も指摘にする |
|
|
43
|
+
| `not-run` | 実行していない(`reason` に理由) | 残す |
|
|
44
|
+
|
|
45
|
+
## 実行の仕組み
|
|
46
|
+
|
|
47
|
+
1. 各検査のルールを、ESLint core・`@typescript-eslint`・`vue`・同梱の `shinaps` から引きます。この版に無いルールの検査は `not-run` になり、他の検査は止まりません。
|
|
48
|
+
2. 検査ごとに、その検査の設定だけを入れたESLintで `calculateConfigForFile` を呼び、ルールが有効なファイルを適用先にします。globの解釈はESLintだけが行います。ESLintが設定を拒否した検査(不正なoptionsなど)は、ここで `not-run` になります。
|
|
49
|
+
3. 同じルール・同じoptionsの検査は1つの設定にまとめ、同じルールでoptionsが違うものだけを別の実行単位にします。型情報を使うルール(`meta.docs.requiresTypeChecking`)は `projectService` を使う単位に分けます。この単位は全てのファイルを typescript-eslint で読み、型の得られないファイル(`tsconfig.json` に含まれないTS・JS、Vue)はそのファイルだけが `not-run` になります。型なしの検査と、他のファイルの型付きの検査は影響を受けません。型情報を使うルールはVueのファイルでは実行できません。
|
|
50
|
+
4. 実行単位ごとに `lintFiles` を1回呼び、組を分類します。指摘は、ESLintがそのファイルに適用すると判定した検査の規約にだけ付けます。
|
|
51
|
+
|
|
52
|
+
## 同梱ルール
|
|
53
|
+
|
|
54
|
+
| ルールID | 内容 | 型情報 |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `shinaps/prefer-promise-with-resolvers` | Promiseのexecutorが受け取る `resolve`・`reject` を、executorの外で使うために変数・プロパティへ代入する書き方(deferred promise)を報告する。`Promise.withResolvers()` で書ける。包んだ関数の代入や、値として渡す呼び出しは報告しない | 不要 |
|
|
57
|
+
|
|
58
|
+
### 同梱ルールの追加
|
|
59
|
+
|
|
60
|
+
規約の検査に既存のルールが使えないとき、レビュー済みでテスト済みのルールをこのパッケージに同梱します。サーバーにはルールのIDとoptionsだけを保存し、コードは配布しません。同梱していないルールは、実装・テスト・レビュー・リリースを経るまで使えません(それまでは、その規約はAI照合のままです)。
|
|
61
|
+
|
|
62
|
+
1. `lib/lint-rules.ts` にルールを足し、`shinapsPlugin.rules` に登録します。IDは意味を変えずに使い続けます(規約の検査はIDで保存され、登録時の意味のレビューがその挙動を前提にするため)。意味が変わるなら別のIDにします。型情報を使うルールには `meta.docs.requiresTypeChecking: true` を付けます(付けないと型なしの実行単位に入り、その単位の検査が全て `not-run` になります)。
|
|
63
|
+
2. `test/lint.test.ts` に、ESLintの `RuleTester` で報告する例・しない例を書きます。規約の good・bad の例と、誤って報告しやすい形(同じ名前の別の変数など)を含めます。型情報を使わないルールは、型情報なしの `RuleTester` で動くことがそのまま確認になります。型情報を使うルールは、`languageOptions.parserOptions` に `projectService` を付けた `RuleTester` で試します。
|
|
64
|
+
3. この文書の「同梱ルール」の表に追加し、通常の変更と同じくレビュー・リリースします。
|
|
65
|
+
4. 規約に検査を付けるのはリリースの後です。規約の全文を読んで coverage を決めます(plugin の `references/lint.md`)。
|
|
66
|
+
|
|
67
|
+
実例: `shinaps/prefer-promise-with-resolvers`(`lib/lint-rules.ts`)。右辺の識別子をscopeで解決し、Promiseのexecutorの引数そのものかを確かめることで、内側の関数の同じ名前の引数を取り違えないようにしています。テストは `test/lint.test.ts` の末尾にあります。
|
|
68
|
+
|
|
69
|
+
## 依存
|
|
70
|
+
|
|
71
|
+
ESLint・typescript-eslint・TypeScript・vue-eslint-parser・eslint-plugin-vue を完全一致の版で依存に持ちます(パッケージの導入が約50MB増えます)。typescript-eslint は TypeScript 7 に対応していないため、TypeScript は6系です(開発用の `tsc` も同じ版を使います)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shinaps/skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Install Shinaps AI workflows and custom agents for Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": "^22.13.0 || >=24"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -36,10 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@biomejs/biome": "2.5.15",
|
|
39
|
-
"@types/node": "^26.6.4"
|
|
40
|
-
"typescript": "^7.0.2"
|
|
39
|
+
"@types/node": "^26.6.4"
|
|
41
40
|
},
|
|
42
41
|
"dependencies": {
|
|
43
|
-
"ajv": "^8.20.0"
|
|
42
|
+
"ajv": "^8.20.0",
|
|
43
|
+
"eslint": "10.14.0",
|
|
44
|
+
"eslint-plugin-vue": "10.12.0",
|
|
45
|
+
"typescript": "6.0.3",
|
|
46
|
+
"typescript-eslint": "8.72.1",
|
|
47
|
+
"vue-eslint-parser": "10.4.1"
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -71,3 +71,7 @@ PR: <draft PR の URL。無ければ なし>
|
|
|
71
71
|
| hooks | 未対応 |
|
|
72
72
|
| permission mode | 定義の `permission_policy` で固定 |
|
|
73
73
|
| セッション予算 | セッション作成時の `max_list_cost` で付ける |
|
|
74
|
+
|
|
75
|
+
## ローカルsnapshotで始める場合
|
|
76
|
+
|
|
77
|
+
初回メッセージにsource.bundleが指定されている場合は、通常のHEAD確認より先に `/mnt/session/uploads/source.bundle` を指定された作業先へcloneする。比較元は初回メッセージのsnapshot SHAとし、GitHubへのアクセスや元の履歴を前提にしない。成果物は上記の一時indexで比較元からの全差分を出力する手順に従い、変更内容・検証結果・未完了事項を `/mnt/session/outputs/report.md` にも残す。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 検証CLIと契約版
|
|
6
6
|
|
|
7
|
-
利用中のskillとCLIの版を確認し、0.
|
|
7
|
+
利用中のskillとCLIの版を確認し、0.45.0のskillには同じ版のnpm CLIを導入する(`npm install -g @shinaps/skills@0.45.0`)。pluginだけにはCLIは含まれない。導入・更新が必要ならその権限を確認し、検証のたびに `npx latest` を取得しない。
|
|
8
8
|
|
|
9
9
|
0.35.0から通常設定の `review_flow.mode` に `ask` を受理する。`config_version` は2のままで、既存のauto・manualの設定は変更不要。0.33.0〜0.34.xのCLIと `$schema` のURLは `ask` を拒否するため、`ask` を使う設定では `$schema` も0.35.0以降のURLにする。
|
|
10
10
|
|