@shinaps/skills 0.44.0 → 0.46.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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/lint-rules.js +55 -1
  3. package/dist/lib/lint.js +33 -3
  4. package/dist/lib/managed-session.js +40 -17
  5. package/dist/lib/managed-snapshot.js +175 -0
  6. package/dist/lib/managed.js +42 -6
  7. package/docs/install.md +1 -1
  8. package/docs/lint.md +6 -3
  9. package/package.json +3 -1
  10. package/plugins/shinaps-claude/.claude-plugin/plugin.json +1 -1
  11. package/plugins/shinaps-claude/managed-agents/skills/dev-managed-cc/references/launch.md +4 -0
  12. package/plugins/shinaps-claude/references/linear/configuration.md +1 -1
  13. package/plugins/shinaps-claude/references/lint.md +3 -3
  14. package/plugins/shinaps-claude/references/managed/configuration.md +1 -1
  15. package/plugins/shinaps-claude/references/managed/configuration.schema.json +1 -1
  16. package/plugins/shinaps-claude/references/managed/execute.md +26 -2
  17. package/plugins/shinaps-claude/references/rule-writing.md +1 -1
  18. package/plugins/shinaps-claude/references/rules.md +16 -3
  19. package/plugins/shinaps-claude/skills/explore/SKILL.md +1 -1
  20. package/plugins/shinaps-claude/skills/extract-rules/SKILL.md +2 -2
  21. package/plugins/shinaps-claude/skills/managed-execute/SKILL.md +8 -2
  22. package/plugins/shinaps-claude/skills/refine/SKILL.md +1 -1
  23. package/plugins/shinaps-codex/.codex-plugin/plugin.json +1 -1
  24. package/plugins/shinaps-codex/managed-agents/skills/dev-managed-cc/references/launch.md +4 -0
  25. package/plugins/shinaps-codex/references/linear/configuration.md +1 -1
  26. package/plugins/shinaps-codex/references/lint.md +3 -3
  27. package/plugins/shinaps-codex/references/managed/configuration.md +1 -1
  28. package/plugins/shinaps-codex/references/managed/configuration.schema.json +1 -1
  29. package/plugins/shinaps-codex/references/managed/execute.md +26 -2
  30. package/plugins/shinaps-codex/references/rule-writing.md +1 -1
  31. package/plugins/shinaps-codex/references/rules.md +16 -3
  32. package/plugins/shinaps-codex/skills/extract-rules/SKILL.md +1 -1
  33. package/plugins/shinaps-codex/skills/managed-execute/SKILL.md +8 -2
package/README.md CHANGED
@@ -8,7 +8,7 @@ Claude Code と Codex で使う、開発ワークフローのスキル集です
8
8
  ## 必要なもの
9
9
 
10
10
  - Claude Code または Codex CLI
11
- - Node.js 22.13 以上の 22 系、または 24 以上(Codex への導入と、設定の検証・Managed Agents の操作・規約の機械検査に使う[配布 CLI](docs/install.md#配布-cli)に必要。機械検査の ESLint の要件に合わせている)。配布 CLI は機械検査のために ESLint・typescript-eslint・TypeScript などを依存に持ち、導入に約 50MB を使います。
11
+ - Node.js 22.13 以上の 22 系、または 24 以上(Codex への導入と、設定の検証・Managed Agents の操作・規約の機械検査に使う[配布 CLI](docs/install.md#配布-cli)に必要。機械検査の ESLint の要件に合わせている)。配布 CLI は機械検査のために ESLint・typescript-eslint・TypeScript などを依存に持ち、導入に約 65MB を使います。
12
12
  - 機能によって追加で必要なもの(Linear・Claude Managed Agents など)は、下の「機能別の資料」の各資料に書いています。
13
13
 
14
14
  ## インストール
@@ -38,6 +38,57 @@ const preferPromiseWithResolvers = {
38
38
  };
39
39
  },
40
40
  };
41
+ /**
42
+ * Vue の `:key` が v-for の index を使う書き方を報告する。index は v-for の 2 番目以降の別名
43
+ * (`(item, i) in items` の i、`(value, name, i) in object` の i) とする。オブジェクトの反復の 2 番目
44
+ * (`(value, name) in object` の name、プロパティ名) も構文では配列の index と区別できないので報告する。
45
+ * `items[i].id` のようにメンバーの計算プロパティとしてだけ使う参照は、index で引いた値の id なので報告しない。
46
+ * テンプレートを解析できないファイル (vue-eslint-parser 以外、HTML 以外のテンプレート) では何もしない。
47
+ */
48
+ const vueNoIndexKey = {
49
+ meta: {
50
+ type: 'problem',
51
+ docs: {
52
+ description: 'Disallow v-for keys derived from the iteration index',
53
+ },
54
+ messages: {
55
+ indexKey: "The key uses the v-for index '{{name}}'. Use a stable identifier of the item (an id or slug).",
56
+ },
57
+ schema: [],
58
+ },
59
+ create(context) {
60
+ // ESLint の parserServices は parser ごとに中身が違う。vue-eslint-parser で読んだときだけ
61
+ // defineTemplateBodyVisitor がある。
62
+ const services = context.sourceCode.parserServices;
63
+ if (!services.defineTemplateBodyVisitor)
64
+ return {};
65
+ // defineTemplateBodyVisitor の戻り値は object 型だが、実体は ESLint に渡す visitor。
66
+ return services.defineTemplateBodyVisitor({
67
+ "VAttribute[directive=true][key.name.name='bind'][key.argument.name='key']"(node) {
68
+ for (const reference of node.value?.references ?? []) {
69
+ const variable = reference.variable;
70
+ if (variable?.kind !== 'v-for')
71
+ continue;
72
+ const forExpression = variable.id.parent;
73
+ if (forExpression?.type !== 'VForExpression')
74
+ continue;
75
+ if (forExpression.left.indexOf(variable.id) < 1)
76
+ continue;
77
+ const parent = reference.id.parent;
78
+ if (parent?.type === 'MemberExpression' &&
79
+ parent.computed &&
80
+ parent.property === reference.id)
81
+ continue;
82
+ context.report({
83
+ node: reference.id,
84
+ messageId: 'indexKey',
85
+ data: { name: reference.id.name },
86
+ });
87
+ }
88
+ },
89
+ });
90
+ },
91
+ };
41
92
  function findVariable(scope, name) {
42
93
  for (let current = scope; current; current = current.upper) {
43
94
  const variable = current.set.get(name);
@@ -48,5 +99,8 @@ function findVariable(scope, name) {
48
99
  }
49
100
  export const shinapsPlugin = {
50
101
  meta: { name: 'shinaps' },
51
- rules: { 'prefer-promise-with-resolvers': preferPromiseWithResolvers },
102
+ rules: {
103
+ 'prefer-promise-with-resolvers': preferPromiseWithResolvers,
104
+ 'vue-no-index-key': vueNoIndexKey,
105
+ },
52
106
  };
package/dist/lib/lint.js CHANGED
@@ -154,18 +154,47 @@ function isActive(config, rule) {
154
154
  const severity = Array.isArray(entry) ? entry[0] : entry;
155
155
  return severity !== undefined && severity !== 0 && severity !== 'off';
156
156
  }
157
+ /**
158
+ * react-x から登録するルール。型情報を使うのに meta.docs.requiresTypeChecking を宣言しないルール
159
+ * (no-implicit-key、no-leaked-conditional-rendering など。ESLintUtils.getParserServices を呼ぶ) は、型なしの
160
+ * 実行単位に入って lintFiles を例外で止め、同じ単位の他の検査まで not-run にするので登録しない。
161
+ * 追加するときは、型を使わないことをソースで確かめる (docs/lint.md)。
162
+ */
163
+ const REACT_X_RULES = [
164
+ 'rules-of-hooks',
165
+ 'exhaustive-deps',
166
+ 'no-array-index-key',
167
+ 'no-missing-key',
168
+ 'no-nested-component-definitions',
169
+ 'no-unstable-context-value',
170
+ ];
171
+ /**
172
+ * react-x が判定の前提にする React の版。settings に版が無いと、react-x (@eslint-react/shared の
173
+ * getReactVersion) は `createRequire(process.cwd())("react")` でプロジェクトの react を読み込むため、固定して
174
+ * 呼ばせない。値は react-x が検出に失敗したときの既定と同じ。React 18 のプロジェクトでも 19 の前提で判定する
175
+ * (`<Context value>` の省略形を provider として扱うなど、報告が増える側に寄る)。
176
+ */
177
+ const REACT_X_SETTINGS = { 'react-x': { version: '19.3.0' } };
157
178
  async function loadRegistry() {
158
- const [{ builtinRules }, tseslint, vue, vueParser] = await Promise.all([
179
+ const [{ builtinRules }, tseslint, vue, vueParser, reactX, regexp] = await Promise.all([
159
180
  import('eslint/use-at-your-own-risk'),
160
181
  import('typescript-eslint'),
161
182
  import('eslint-plugin-vue'),
162
183
  import('vue-eslint-parser'),
184
+ import('eslint-plugin-react-x'),
185
+ import('eslint-plugin-regexp'),
163
186
  ]);
164
- // typescript-eslint と eslint-plugin-vue が公開する型は ESLint の Plugin・Parser・RuleModule の型と
165
- // 細部が一致しない (どちらも実行時は ESLint の API に従う)。flat config に渡すために ESLint の型にする。
187
+ // typescript-eslint・eslint-plugin-vue・eslint-plugin-react-x が公開する型は ESLint の Plugin・Parser・
188
+ // RuleModule の型と細部が一致しない (どれも実行時は ESLint の API に従う)。flat config に渡すために ESLint の型にする。
189
+ const reactXRules = reactX.default.rules;
166
190
  const plugins = {
167
191
  '@typescript-eslint': tseslint.default.plugin,
168
192
  vue: vue.default,
193
+ 'react-x': {
194
+ meta: reactX.default.meta,
195
+ rules: Object.fromEntries(REACT_X_RULES.map((name) => [name, reactXRules[name]])),
196
+ },
197
+ regexp: regexp.default,
169
198
  shinaps: shinapsPlugin,
170
199
  };
171
200
  const rules = new Map(builtinRules);
@@ -226,6 +255,7 @@ function unitConfig(registry, root, typed, rules) {
226
255
  ...rules.map((planned) => ({
227
256
  files: [...new Set(planned.bindings.flatMap((binding) => binding.check.files))],
228
257
  plugins: registry.plugins,
258
+ settings: REACT_X_SETTINGS,
229
259
  // options は JSON の配列 (unknown[]) で、ESLint の RuleEntry は要素の型を持たないので型だけを合わせる。
230
260
  rules: { [planned.rule]: ['error', ...planned.options] },
231
261
  })),
@@ -4,6 +4,7 @@ import { join, resolve } from 'node:path';
4
4
  import { readJson, readJsonIfExists, writeFileAtomic } from "./fs.js";
5
5
  import { withLock } from "./lock.js";
6
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
- type: 'github_repository',
70
- url: request.repository.url,
71
- mount_path: request.repository.mount_path,
72
- ...(options.githubToken ? { authorization_token: options.githubToken } : {}),
73
- checkout: { type: 'commit', sha: request.base_sha },
74
- },
75
- ];
76
- if (options.claudeMdPath) {
77
- // upload は記録の後に行う。記録の前だと、途中で止まった実行の再実行のたびに Files が増える。
78
- let fileId;
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(options.claudeMdPath),
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
- resources.push({ type: 'file', file_id: fileId, mount_path: '/CLAUDE.md' });
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);
@@ -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
+ }
@@ -1,21 +1,26 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
3
4
  import { errorMessage } from "./fs.js";
4
5
  import { managedAccountDir, managedRoot, packageRoot } from "./layout.js";
5
6
  import { antCommand } from "./managed-ant.js";
6
7
  import { validateManagedConfig } from "./managed-config.js";
7
- import { collectOutputs, permissions, replySession, runSession, sessionStatus, staffings, } from "./managed-session.js";
8
+ import { collectOutputs, importSession, permissions, replySession, runSession, sessionStatus, staffings, } from "./managed-session.js";
8
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]+(-[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 single hyphens.`);
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/docs/install.md CHANGED
@@ -26,7 +26,7 @@ npx @shinaps/skills@latest recover codex
26
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.44.0
29
+ npm install -g @shinaps/skills@0.46.0
30
30
  shinaps-skills --version
31
31
  ```
32
32
 
package/docs/lint.md CHANGED
@@ -4,7 +4,7 @@ Shinaps AIの規約が持つ機械検査(`checks`)を手元で実行し、
4
4
 
5
5
  ## 前提と保証
6
6
 
7
- - プロジェクトの変更は要りません。プロジェクトのlint設定(`eslint.config.*`・`.oxlintrc.json`・`bunfig.toml` など)・`package.json` のscript・`node_modules` のパッケージは読み込みません。ESLintのNode APIを `overrideConfigFile: true` で使い、パーサーとプラグインはこのパッケージの依存から渡します。読むのは対象ファイルと、型情報を使うルールのための `tsconfig.json`・型宣言だけです(実行はしません)。
7
+ - プロジェクトの変更は要りません。プロジェクトのlint設定(`eslint.config.*`・`.oxlintrc.json`・`bunfig.toml` など)・`package.json` のscript・`node_modules` のパッケージは読み込みません。ESLintのNode APIを `overrideConfigFile: true` で使い、パーサーとプラグインはこのパッケージの依存から渡します。読むのは対象ファイルと、型情報を使うルールのための `tsconfig.json`・型宣言だけです(実行はしません)。React のルール(react-x)が判定に使う React の版は、プロジェクトの `react` から検出させず `19.3.0` に固定します(React 18 のプロジェクトでも 19 の前提で判定するので、`<Context value>` の省略形を provider として扱うなど、報告が増える側に寄ります)。
8
8
  - 対象ファイルへ書き込みません(自動修正はしません)。結果はローカルのファイルに書き、サーバーへは送りません。
9
9
  - プロジェクトの `eslint-disable` コメントでは検査を抑止しません(`allowInlineConfig: false`)。プロジェクトの都合の例外は、指摘を確認する側で扱います。
10
10
  - 組をAI照合から外してよいのは、coverage が `full` の検査が、ESLintの判定でそのファイルに適用され、検査の実行単位が完了し、そのファイルに致命的な報告もruleIdの無い報告も無いときだけです。それ以外の組は `not-run`(または `partial`)として返し、合格にしません。coverage は検査を登録したときの判断で、このCLIは値を読むだけです。
@@ -44,7 +44,9 @@ shinaps-skills lint --checks <checks.json> --root <project> --out <result.json>
44
44
 
45
45
  ## 実行の仕組み
46
46
 
47
- 1. 各検査のルールを、ESLint core・`@typescript-eslint`・`vue`・同梱の `shinaps` から引きます。この版に無いルールの検査は `not-run` になり、他の検査は止まりません。
47
+ 1. 各検査のルールを、ESLint core・`@typescript-eslint`・`vue`・`react-x`(下の6ルールだけ)・`regexp`・同梱の `shinaps` から引きます。この版に無いルールの検査は `not-run` になり、他の検査は止まりません。
48
+ - `react-x`(eslint-plugin-react-x): `rules-of-hooks`・`exhaustive-deps`・`no-array-index-key`・`no-missing-key`・`no-nested-component-definitions`・`no-unstable-context-value`。react-x には型情報を使うのに `meta.docs.requiresTypeChecking` を宣言しないルールがあり、型なしの実行単位に入ると単位の全ての検査を止めるため、型を使わないことをソースで確かめたルールだけを登録します(`lib/lint.ts` の `REACT_X_RULES`)。他の外部プラグインでも、型を使うのに宣言しないルールは登録しません。
49
+ - `regexp`(eslint-plugin-regexp): 全てのルール。型情報は有無を確かめてから使います。
48
50
  2. 検査ごとに、その検査の設定だけを入れたESLintで `calculateConfigForFile` を呼び、ルールが有効なファイルを適用先にします。globの解釈はESLintだけが行います。ESLintが設定を拒否した検査(不正なoptionsなど)は、ここで `not-run` になります。
49
51
  3. 同じルール・同じoptionsの検査は1つの設定にまとめ、同じルールでoptionsが違うものだけを別の実行単位にします。型情報を使うルール(`meta.docs.requiresTypeChecking`)は `projectService` を使う単位に分けます。この単位は全てのファイルを typescript-eslint で読み、型の得られないファイル(`tsconfig.json` に含まれないTS・JS、Vue)はそのファイルだけが `not-run` になります。型なしの検査と、他のファイルの型付きの検査は影響を受けません。型情報を使うルールはVueのファイルでは実行できません。
50
52
  4. 実行単位ごとに `lintFiles` を1回呼び、組を分類します。指摘は、ESLintがそのファイルに適用すると判定した検査の規約にだけ付けます。
@@ -54,6 +56,7 @@ shinaps-skills lint --checks <checks.json> --root <project> --out <result.json>
54
56
  | ルールID | 内容 | 型情報 |
55
57
  | --- | --- | --- |
56
58
  | `shinaps/prefer-promise-with-resolvers` | Promiseのexecutorが受け取る `resolve`・`reject` を、executorの外で使うために変数・プロパティへ代入する書き方(deferred promise)を報告する。`Promise.withResolvers()` で書ける。包んだ関数の代入や、値として渡す呼び出しは報告しない | 不要 |
59
+ | `shinaps/vue-no-index-key` | Vueの `:key` が v-for の2番目以降の別名(配列の index)を参照する書き方を報告する。オブジェクトの反復の2番目(プロパティ名)も構文では区別できないので報告する。`items[i].id` のようにメンバーの計算プロパティとしてだけ使う参照は報告しない。HTML以外のテンプレート(`lang="pug"` など)は解析しないので何も報告しない | 不要 |
57
60
 
58
61
  ### 同梱ルールの追加
59
62
 
@@ -68,4 +71,4 @@ shinaps-skills lint --checks <checks.json> --root <project> --out <result.json>
68
71
 
69
72
  ## 依存
70
73
 
71
- ESLint・typescript-eslint・TypeScript・vue-eslint-parser・eslint-plugin-vue を完全一致の版で依存に持ちます(パッケージの導入が約50MB増えます)。typescript-eslint は TypeScript 7 に対応していないため、TypeScript は6系です(開発用の `tsc` も同じ版を使います)。
74
+ ESLint・typescript-eslint・TypeScript・vue-eslint-parser・eslint-plugin-vue・eslint-plugin-react-x・eslint-plugin-regexp を完全一致の版で依存に持ちます(パッケージの導入は約65MBになります)。外部プラグインの版を上げるときは、そのルールを使う公式規約の checks の coverage がルールの挙動を前提にしているので、規約の Good・Bad と見逃しの境界の例を同じ版で実行し直して確かめます。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.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Install Shinaps AI workflows and custom agents for Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,8 @@
41
41
  "dependencies": {
42
42
  "ajv": "^8.20.0",
43
43
  "eslint": "10.14.0",
44
+ "eslint-plugin-react-x": "5.28.8",
45
+ "eslint-plugin-regexp": "3.3.2",
44
46
  "eslint-plugin-vue": "10.12.0",
45
47
  "typescript": "6.0.3",
46
48
  "typescript-eslint": "8.72.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shinaps",
3
3
  "description": "Shinaps AIの開発ワークフロー。プロジェクトの指示から条件を読み、調査・設計・実装・簡素化・レビュー・コミット・PR・リリースとLinearでの開発進行を支援する。",
4
- "version": "0.44.0",
4
+ "version": "0.46.0",
5
5
  "author": {
6
6
  "name": "shinaps"
7
7
  },
@@ -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.44.0のskillには同じ版のnpm CLIを導入する(`npm install -g @shinaps/skills@0.44.0`)。pluginだけにはCLIは含まれない。導入・更新が必要ならその権限を確認し、検証のたびに `npx latest` を取得しない。
7
+ 利用中のskillとCLIの版を確認し、0.46.0のskillには同じ版のnpm CLIを導入する(`npm install -g @shinaps/skills@0.46.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
 
@@ -28,7 +28,7 @@ Shinaps AIの規約は、機械検査(`checks`)を持てる。検査は配
28
28
 
29
29
  [Shinaps規約の書き方](rule-writing.md)で書き込む規約に、検査を付けられるかを判断する。
30
30
 
31
- - **使えるルール**: ESLint core、`@typescript-eslint/*`(型情報を使うものを含む)、`vue/*`、同梱の `shinaps/*`(`docs/lint.md` の一覧)。版によって違うので、同じ版のCLIで確かめる。使えるルールが無い規約には検査を付けない(AI照合のまま)。同梱ルールの追加は `docs/lint.md` の手順で別の作業として提案する。
32
- - **coverageの判断**: 規約の全文(scope・labelsを含み、更新では更新後の全文)と、検査の `rule`・`options`・`files` を照らす。`files` の範囲で、規約が求めることへの違反をこのルールが全て報告すると言えるときだけ `full`、見逃しが一つでもありうれば `partial`。lintが規約より広く報告する(例外も報告する)ことは `full` の妨げにならない(指摘はメインが確認する)。例で通ることは根拠にならない。
31
+ - **使えるルール**: ESLint core、`@typescript-eslint/*`(型情報を使うものを含む)、`vue/*`、`react-x/*` のうち `rules-of-hooks`・`exhaustive-deps`・`no-array-index-key`・`no-missing-key`・`no-nested-component-definitions`・`no-unstable-context-value`、`regexp/*`、同梱の `shinaps/*`(`docs/lint.md` の一覧)。版によって違うので、同じ版のCLIで確かめる。使えるルールが無い規約には検査を付けない(AI照合のまま)。同梱ルールの追加は `docs/lint.md` の手順で別の作業として提案する。
32
+ - **coverageの判断**: 規約の全文(scope・labelsを含み、更新では更新後の全文)と、検査の `rule`・`options`・`files` を照らす。`files` の範囲で、規約が求めることへの違反をこのルールが全て報告すると言えるときだけ `full`、見逃しが一つでもありうれば `partial`。lintが規約より広く報告する(例外も報告する)ことは `full` の妨げにならない(指摘はメインが確認する)。例で通ることは根拠にならない。Vueのテンプレートを見るルール(`vue/*` のテンプレート用、`shinaps/vue-no-index-key`)は、HTML以外のテンプレート(`lang="pug"` など)を解析せず報告なしで終わるので、それだけでは `.vue` を対象に `full` にできない。
33
33
  - **補助の確認**: 規約のgood・badの例を一時ディレクトリにファイルとして書き、同じ入口で実行して、badで報告され、goodで報告されないことを確かめる。対象プロジェクトのコードを写さない。
34
- - **書き込み**: `record_rule`・`update_rule` の `checks` に渡す。一括の `record_rules` は `checks` を受け取らない(付けると呼出し全体が入力エラーになる)ので、検査は登録後に `update_rule` で付ける。`update_rule` は配列ごと置換し、`[]` で外す。検査を持つ規約の内容(出典以外)を変える更新では、更新後の全文で検査を見直し、同じ要求で `checks` を渡す。渡さないと `CHECKS_REQUIRED` で保存されない。見直した結果が同じなら同じ配列を渡す。
34
+ - **書き込み**: `record_rule`・`update_rule` の `checks` に渡す。一括の `record_rules` は `checks` を受け取らない(付けると呼出し全体が入力エラーになる)ので、検査は登録後に `update_rule` で付ける。`update_rule` は配列ごと置換し、`[]` で外す。検査を持つ規約の内容(出典とrepoの表示名以外。適用先はscope、repo_ownerのowner、repoのrepoIdで比べる)を変える更新では、更新後の全文で検査を見直し、同じ要求で `checks` を渡す。渡さないと `CHECKS_REQUIRED` で保存されない。見直した結果が同じなら同じ配列を渡す。
@@ -4,7 +4,7 @@ Managed Agents は配布CLI `shinaps-skills managed` で導入・依頼・観測
4
4
 
5
5
  ## アカウント
6
6
 
7
- アカウントは、APIキー1つが属する組織・workspace に利用者が付ける名前で、英小文字・数字と単独のハイフン(`^[a-z0-9]+(-[a-z0-9]+)*$`)にする。同じマシンでは、同じ名前は常に同じ組織・workspace を表すようにする。lock がアカウント名で引かれるので、別の組織に同じ名前を使うと別の組織の lock で適用しようとする(`ant apply` は lock と違う組織・workspace の資格情報を拒否して止まる)。
7
+ アカウントは、APIキー1つが属する組織・workspace に利用者が付ける名前で、先頭は英小文字または数字、以降は英小文字・数字とハイフン(`^[a-z0-9][a-z0-9-]*$`)にする。同じマシンでは、同じ名前は常に同じ組織・workspace を表すようにする。lock がアカウント名で引かれるので、別の組織に同じ名前を使うと別の組織の lock で適用しようとする(`ant apply` は lock と違う組織・workspace の資格情報を拒否して止まる)。
8
8
 
9
9
  ### APIキー
10
10
 
@@ -30,7 +30,7 @@
30
30
  "type": "object",
31
31
  "minProperties": 1,
32
32
  "propertyNames": {
33
- "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
33
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
34
34
  },
35
35
  "additionalProperties": {
36
36
  "type": "object",
@@ -63,7 +63,7 @@ shinaps-skills managed collect --account <name> --session <ID>
63
63
  ```
64
64
 
65
65
  - セッションに紐づく Files(`changes.patch` と作業記録の tar)を、そのアカウントの `sessions/<ID>/outputs/` へ保存し、保存したパスを表示する。同じ名前のファイルは最新のものだけを保存する(lead は毎ターン `changes.patch` を比較元からの全体の差分で置き換える)。名前がファイル名だけでないものは保存せずに止まる。保存したサイズは一覧の値と比べる。
66
- - CLI は差分を適用しない。ローカルの CLI は git を呼ばず、自分では push しない。push と draft PR の作成は、許可した場合にセッション内の lead が行う。`changes.patch` は合意した base の SHA のチェックアウトで `git apply --check` してから、範囲と受入条件に対する差分を確かめる。適用するかは利用者と決める。
66
+ - collect は差分を適用せず、保存だけを行い、自分では push しない。ローカルsnapshotのprepare・importはGitを使い、importは新しいディレクトリにだけ差分を適用する。既存の作業ツリーへは適用しない。push と draft PR の作成は、許可した場合にセッション内の lead が行う。`changes.patch` は合意した base の SHA のチェックアウトで `git apply --check` してから、範囲と受入条件に対する差分を確かめる。適用するかは利用者と決める。
67
67
  - `コミット:` と push 先がある場合は `git fetch origin <head>` で head ブランチを取得し、base の SHA からのコミットと差分を確かめる。
68
68
  - `PR:` がある場合は `gh pr view <PR の URL> --json isDraft,baseRefName,headRefName` または GitHub MCP の読み取りで、draft であること・base と head が合意どおりであることを確かめる。
69
69
 
@@ -74,9 +74,33 @@ shinaps-skills managed collect --account <name> --session <ID>
74
74
  ## 未検証の境界
75
75
 
76
76
  - `managed setup`・`run`・`status`・`reply`・`collect` のコマンド・フラグ・stdin の本文・出力の形は ant 2.4.0 の公式ソースで照合し、偽の `ant` を使うテストで確かめた。実際の `ant` とクラウドではまだ実行していない。
77
- - API の側で決まり、ant のソースでは確かめられないこと: 予算 `max_list_cost.amount` がセント単位か、`mount_path: /CLAUDE.md` が `/mnt/session/uploads/CLAUDE.md` に置かれるか、Files の upload・一覧・取得に `files-api-2025-04-14` と `managed-agents-2026-04-01` の両方の beta を付ければよいか(ant の `--beta` は既定の beta を置き換えるので、全ての呼び出しで明示している)、Files の応答の `filename`・`size_bytes`・`created_at`、events の一覧に役割のスレッドの `agent.message` が混ざるか。lock が無い状態での skill の重複は、同じ setup で作る lead の同名確認で止まる。
77
+ - API の側で決まり、ant のソースでは確かめられないこと: `mount_path: /CLAUDE.md` が `/mnt/session/uploads/CLAUDE.md` に置かれるか、Files の upload・一覧・取得に `files-api-2025-04-14` と `managed-agents-2026-04-01` の両方の beta を付ければよいか(ant の `--beta` は既定の beta を置き換えるので、全ての呼び出しで明示している)、Files の応答の `filename`・`size_bytes`・`created_at`、events の一覧に役割のスレッドの `agent.message` が混ざるか。lock が無い状態での skill の重複は、同じ setup で作る lead の同名確認で止まる。
78
78
  - マウントしたリポジトリの remote へ `git push` できるか(`authorization_token` が push に使われるか)。最初のセッションで最優先に確かめ、できなければ GitHub MCP の push 用ツール(`create_branch`・`push_files`)を有効にするかを利用者と相談する。
79
79
  - commit をチェックアウトしてマウントした作業先から、origin の head ブランチを取得できるか(clone の深さと fetch の資格情報)。
80
80
  - GitHub MCP で draft PR を作れるか。`draft: true` は lead への指示でしか担保されない。
81
81
  - vault に資格情報を登録できたことは、上の3点の確認にならない。
82
82
  - 完了したスレッドが同時実行の枠(25本)を空けるか。ULTRAでは、ラウンドごとに新しく起動する担当を含めて、同時に保つスレッドが多い。
83
+
84
+ ## Routerとローカルコードの往復
85
+
86
+ Claude Account Routerを使う場合は、アプリでAPIキーとクラウド用アカウントを登録し、アプリ内CLIの `cloud managed` 経由で同じmanagedコマンドを呼ぶ。setupとrunはRouterの選択アカウントを使える。status・reply・collect・importは作成結果のaccountとsession_idを明示し、途中の選択変更を引き継がない。キーを取得して会話や設定へ書き出さない。
87
+
88
+ GitHubへpushしていないコードも渡す場合は、先にローカルで固定する。
89
+
90
+ ```sh
91
+ shinaps-skills managed prepare --source /path/to/repository
92
+ ```
93
+
94
+ 戻り値のfilesとmanifestを確認する。追跡中の現在の内容と、Gitで無視されていない新規ファイルを含む。履歴・Git設定・node_modules・.local・.shinapsは含まない。資格情報ファイルやcheckout外へのシンボリックリンクは拒否する。内容の秘密検出は行わないので、送信対象の確認は必要。submoduleはこの方式では扱わない。
95
+
96
+ 合意済みのrunに `--snapshot <manifest>` を付ける。比較元のSHAは保存済みsnapshotから決まり、`--base-sha` は不要。体制・許可・予算・範囲は通常どおり明示する。ローカル方式はpush・draft_prを受理しない。snapshotはFiles APIで送り、GitHubリポジトリのマウントやGitHub tokenを使わない。元のGit履歴は含まれない。
97
+
98
+ collectの後、保存されたセッションを新しいローカルcheckoutへ戻せる。
99
+
100
+ ```sh
101
+ shinaps-skills managed import --account personal --session SESSION_ID --destination /path/to/new-checkout
102
+ ```
103
+
104
+ importはAPIキー不要。保存された送信時点のコード(`~/.local/share/shinaps-ai/skills/snapshots/<SHA>/`。アカウントに属さない。prepareごとに複製とbundleが残るので、importと確認が済んだら削除してよい)を復元し、そのセッションのchanges.patchをindexへ適用する。既存のdestinationや元の作業ツリーは上書きしない。適用失敗時は作成したディレクトリを削除してエラーにする。復元先で差分とreport.mdの報告を読み、ローカルの検証を行って作業を再開する。元のブランチへの統合は別途差分を確認して行う。
105
+
106
+ 実クラウドへの送信・実行・回収は未検証。ローカルの代替antと実Gitでの往復テストは、クラウド環境でのgit利用や成果物生成の保証ではない。予算amountのセント単位は[公式仕様](https://platform.claude.com/docs/en/managed-agents/budgets)で確認済みで、リクエスト途中では停止しないため厳密な請求上限とは異なる。
@@ -60,7 +60,7 @@ coding の規約は、使えるlintのルールで検査できるかを判断し
60
60
 
61
61
  ## scope
62
62
 
63
- 全リポジトリに当てはまる判断だけ `scope: user` にして、他のプロジェクトでも通じる表現で書く。同じownerの配下に共通する判断は `scope: repo_owner`、特定のリポジトリの判断は `scope: repo` にして具体的に書く。
63
+ 全リポジトリに当てはまる判断だけ `scope: user` にして、他のプロジェクトでも通じる表現で書く。同じownerの配下に共通する判断は `scope: repo_owner`、特定のリポジトリの判断は `scope: repo` にして具体的に書く。`scope: repo` で書くには、[Shinaps規約の取得](rules.md)の「リポジトリを特定する」でrepositoryの `id` を得る。
64
64
 
65
65
  ## 目安と上限
66
66
 
@@ -25,12 +25,25 @@ Shinaps AIのMCPから規約を取得する手順。取得は呼出し元(メ
25
25
 
26
26
  対象が確定していない工程(devの入口・`spec`・`explore`・`debug`)は、論点の技術を語彙から推定してよい。`filterUserRulesByLabels` は指定しない。
27
27
 
28
+ ## リポジトリを特定する
29
+
30
+ `scope: repo` の規約はGitHubのrepositoryの`id`で識別し、owner・名前は表示にだけ使う。名前は改名で変わり、旧名は別のrepositoryやforkが使えるので、名前の一致を同じrepositoryの根拠にしない。
31
+
32
+ 1. 対象のownerと名前を決める。既定は現在のGit workspaceの `git remote get-url origin`(`github.com/<owner>/<repo>(.git)?` の形)。登録先が別のrepositoryなら、その値にする。この形に当たらなければ(GitHub以外のremoteなど)、`repoOwner`・`repoName`・`repoId` のどれも渡さない。
33
+ 2. `gh api repos/<owner>/<repo> --jq '{id, full_name}'` を1回呼ぶ。改名前の名前でもGitHubは改名後のrepositoryを返すので、`id` を `repoId`、`full_name` の `/` の前後を `repoOwner`・`repoName` にする。
34
+ 3. 呼出しが失敗したら(`gh` が無い、認証が無い、404など。理由は区別しない)、`repoId`・`repoName` は渡さない。1で決めたownerだけを `repoOwner` にする。
35
+
36
+ - **取得**: `search_rules` には `repoOwner` と `repoId` を渡し、`repoName` は渡さない(入力エラーになる)。`repoId` が無いときは `scope: repo` の規約が返らないので、「リポジトリを特定できず、このrepositoryの規約は取得していない」と1文で報告する。
37
+ - **新規の書込み**: `scope: repo` の `record_rule`・`record_rules` には `repoOwner`・`repoName`・`repoId` をそろえて渡す。
38
+ - **更新**: `update_rule` で `scope: "repo"` と3つをそろえて渡すのは、適用先を repo にするとき、別のrepositoryに変えるとき、表示名を現在の名前にするとき(2の `full_name` が規約の `repoOwner`・`repoName` と違うとき)だけ。内容だけの更新では適用先の項目を渡さない(`scope` なしで渡すと入力エラーになる)。
39
+ - **`repoId` が得られないとき**: `scope: repo` では書き込まず、`scope: user`・`scope: repo_owner` に変えるか中止するかを利用者に確認する。範囲を変えるときは、[Shinaps規約の書き方](rule-writing.md)の比較をやり直す。
40
+
28
41
  ## 一覧を取る
29
42
 
30
- `search_rules` を `fields: "summary"` で呼ぶ。`repoOwner`・`repoName` は現在のGit workspaceから渡す。対象が確定した照合では `filterUserRulesByLabels: true` を渡す。
43
+ `search_rules` を `fields: "summary"` で呼ぶ。`repoOwner`・`repoId` は「リポジトリを特定する」で決めた値を渡す。対象が確定した照合では `filterUserRulesByLabels: true` を渡す。
31
44
 
32
45
  ```json
33
- { "fields": "summary", "repoOwner": "...", "repoName": "...", "languages": ["typescript", "javascript"], "platforms": ["cloudflare-workers"], "frameworks": ["hono"], "filterUserRulesByLabels": true }
46
+ { "fields": "summary", "repoOwner": "...", "repoId": 123456789, "languages": ["typescript", "javascript"], "platforms": ["cloudflare-workers"], "frameworks": ["hono"], "filterUserRulesByLabels": true }
34
47
  ```
35
48
 
36
49
  - サーバーは同じ軸をOR、指定した軸の間をANDで判定する。
@@ -60,7 +73,7 @@ Shinaps AIのMCPから規約を取得する手順。取得は呼出し元(メ
60
73
 
61
74
  [Shinaps規約の書き方](rule-writing.md)の比較に使う規約は、次の条件で取得する。
62
75
 
63
- - `search_rules` は `fields: "summary"` で全ページ取得し、`filterUserRulesByLabels` と `kind` を渡さない。`repoOwner`・`repoName` は、登録先のowner・repoがあればその値、無い分は現在のGit workspaceの値にする。
76
+ - `search_rules` は `fields: "summary"` で全ページ取得し、`filterUserRulesByLabels` と `kind` を渡さない。`repoOwner`・`repoId` は「リポジトリを特定する」で決めた値にする。
64
77
  - 公式規約も一覧に載るよう、`list_search_facets` の語彙が8件以内の技術軸(`languages` など)を一つ選び、その全slugを渡す。技術軸はその軸のlabelが無い公式規約にも一致する。`topics` は一致必須で別のtopicの公式規約が返らなくなるので渡さない。渡せる軸が無ければ公式規約の取得は未完了とする。
65
78
  - `projectVersions` には確定した版だけを渡す。渡しても渡さなくても、条件に合わない条件付きの公式規約は返らない。
66
79
  - 一覧のtitleとlabelから同じ場面を扱う可能性を否定できない規約を選び、「全文を取る」で全文を取得する。文字列の一致で選ばない(公式規約のtitleは英語)。「全文を取る規約を選ぶ」は対象ファイルとの照合の基準で、ここには適用しない。
@@ -76,7 +76,7 @@ Claude Codeの `Skill`・`Agent`・`AskUserQuestion`・`TaskCreate` を利用で
76
76
  ### Phase 1: セッション初期化
77
77
 
78
78
  1. 最初の質問を受領する。引数が空、またはファイルパスだけなら、理解したいことを `AskUserQuestion` で 1 問だけ確認する
79
- 2. git remoteから `repoOwner` / `repoName` を解決する。今回の依頼に関連する既存の spec・調査レポート・Git の変更履歴を確認し、引き継ぐ決定と根拠を要約する。既存成果物がなければ追加取得は不要。
79
+ 2. [Shinaps規約の取得](../../references/rules.md) の「リポジトリを特定する」で `repoOwner` / `repoId` を解決する。今回の依頼に関連する既存の spec・調査レポート・Git の変更履歴を確認し、引き継ぐ決定と根拠を要約する。既存成果物がなければ追加取得は不要。
80
80
  3. [Shinaps規約の取得](../../references/rules.md) に従い、最初の質問に対応する推定 facet で一覧を取り、必要な規約全文を取得する。論点が別領域へ移ったら、その時点で facet を取り直す
81
81
  4. リポジトリの `CLAUDE.md` / `AGENTS.md` / その他の指示ファイルを読む
82
82
  5. `git status --short --branch` と HEAD を記録する。調査対象の revision は未コミット変更を含む現在の worktree をデフォルトにし、production / PR / commit が明示された場合は対象 revision を特定して別に記録する。reset / stash / checkout / clean は行わない
@@ -28,7 +28,7 @@ Claude Codeの `Skill`・`Agent`・`AskUserQuestion`・`TaskCreate` を利用で
28
28
  2. 失敗したら `git remote get-url origin` を parse (`github.com/<owner>/<repo>(.git)?` 形式)
29
29
  3. それでも決まらないなら **ユーザーにチャットで確認** (「対象は `<owner>/<repo>` でいいですか?」)
30
30
 
31
- 解決した `<owner>/<repo>` をユーザーに **必ず提示** し、誤りなら別の repo を入力してもらう。
31
+ 解決した `<owner>/<repo>` をユーザーに **必ず提示** し、誤りなら別の repo を入力してもらう。確定した repo を [Shinaps規約の取得](../../references/rules.md) の「リポジトリを特定する」で特定し、`repoId` と現在の `repoOwner`・`repoName` を得る。`repoId` が得られなければ `scope: repo` では投入できないので、ここで止めて利用者に確認する。
32
32
 
33
33
  ## Step 1. since (期間) をチャットで確認
34
34
 
@@ -87,7 +87,7 @@ Good/Bad サンプルコード (examples) の付け方:
87
87
 
88
88
  投入手順:
89
89
 
90
- - 候補を確定してから、[Shinaps規約の書き方](../../references/rule-writing.md) の「書き込む前に既存の規約と比べる」で候補ごとに結果を決める。既存の規約への**更新**は下の差分確認へ進める。**提示** はブロックの終わりにまとめて利用者へ示し、残りの投入は止めない。**新規**候補だけを集めて `record_rules({ scope: 'repo', repoOwner, repoName, rules: [{ title, why, recommend?, avoid?, kind?, labels?, examples?, sourceUrls? }, ...] })` で **1 call bulk 投入** する。上限は MCP ツールの入力の説明に従う。`labels` は language / platform / framework / topic の slug 配列 (`list_search_facets` が返す公式カタログの slug のみ、language/platform/framework は 1 rule に各 1 つまで) (1 call 最大 100 件、超えたら分割。同一 title の重複は入力の schema 検証で拒否される)。
90
+ - 候補を確定してから、[Shinaps規約の書き方](../../references/rule-writing.md) の「書き込む前に既存の規約と比べる」で候補ごとに結果を決める。既存の規約への**更新**は下の差分確認へ進める。**提示** はブロックの終わりにまとめて利用者へ示し、残りの投入は止めない。**新規**候補だけを集めて `record_rules({ scope: 'repo', repoOwner, repoName, repoId, rules: [{ title, why, recommend?, avoid?, kind?, labels?, examples?, sourceUrls? }, ...] })` で **1 call bulk 投入** する。上限は MCP ツールの入力の説明に従う。`labels` は language / platform / framework / topic の slug 配列 (`list_search_facets` が返す公式カタログの slug のみ、language/platform/framework は 1 rule に各 1 つまで) (1 call 最大 100 件、超えたら分割。同一 title の重複は入力の schema 検証で拒否される)。
91
91
  - extract-rules は対象 repo の PR レビューから抽出する性質上 scope=repo (当該 repo 限定) がデフォルト。横断ルールとして残したい場合だけユーザー判断で `scope: 'user'` に格上げし、書き直した全文で比べ直す。**格上げするときは why / recommend / avoid / examples を当該 repo の固有文脈 (内部 API 名・モジュール構成・ドメイン固有の識別子) から切り離し、他プロジェクトでも適用場面を想定できる汎用的な表現・汎用サンプルに解釈し直してから登録する** (scope=repo のままなら repo 固有の具体的な記述・コード片で良い)
92
92
  - `RULE_TOO_LARGE` が返ったら、その呼出し分は全体として保存されていない。details の limit・actual・title を確かめ、該当候補の本文・例・出典を短くしてから同じ呼出しをやり直す。`update_rule` の `RULE_UPDATE_CONFLICT` は全文を読み直してから再試行する。
93
93
  - `RULE_LIMIT_REACHED` が返ったら投入を止める。個人ルールの保存数には上限があり、残りの枠が足りない `record_rules` はその呼出し分が全体として失敗し、一部も保存されない (件数を減らして一部だけ登録する手順へ自動では切り替えない)。それより前の呼出しで投入した分は保存されている。details の `limit` と `actual` から残りの枠を示し、投入する件数を絞るか既存ルールを整理するかのどちらにするかを利用者に確認する。以降のブロックの抽出・投入にも進まず、利用者の選択を待つ。
@@ -9,10 +9,16 @@ description: Claude Managed Agents の lead へ、アカウントを選んで de
9
9
 
10
10
  # Managed Agents へ依頼する
11
11
 
12
- [設定とアカウント](../../references/managed/configuration.md)と[依頼と観測の手順](../../references/managed/execute.md)を読んで進める。操作は配布CLIの `managed run`・`status`・`reply`・`collect` で行う。
12
+ [設定とアカウント](../../references/managed/configuration.md)と[依頼と観測の手順](../../references/managed/execute.md)を読んで進める。操作は配布CLIの `managed prepare`・`run`・`status`・`reply`・`collect`・`import` で行う。
13
13
 
14
14
  - 呼び出しのたびにアカウント・base ブランチと SHA・head ブランチ・範囲と受入条件・体制(LITE・BASIC・ULTRA)・許可・予算を利用者と合意し、引数で明示する。設定・lock・過去の作業から推定しない。
15
15
  - 設定(`.shinaps/managed.json`)が無いか旧版なら、手順の「設定を用意する」に従って一問ずつ確認して作る。設定から読むのは `repository` とアカウントの `vault_id` だけで、lead と環境の ID はアカウントの lock から読む。lock が無ければ止め、導入は managed-setup で行う。
16
16
  - 計画・実装・レビューの工程と追加の担当は lead に任せ、ここでは指示しない。
17
- - 回収した差分は適用しない。確認と適用の判断は利用者と行う。push・merge・deploy・release・Issue の更新・セッションのアーカイブは行わない。Linear の進行とは独立した入口である。
17
+ - 回収した差分を既存の作業ツリーへ適用しない(importは新しいディレクトリにだけ復元する)。確認と統合の判断は利用者と行う。push・merge・deploy・release・Issue の更新・セッションのアーカイブは行わない。Linear の進行とは独立した入口である。
18
18
  - キーと token は利用者が環境変数に設定する。受け取らない・表示しない・記録しない。
19
+
20
+ ## Routerとローカルでの再開
21
+
22
+ 利用者がClaude Account Routerとの連携を選んだ場合は、アプリ内CLIの `cloud managed` を使う。APIキーを読み出して会話へ返さない。作成結果のaccountとsession_idを保存し、後続操作でその組を使う。
23
+
24
+ 未コミットのローカルコードを渡す依頼では、[ローカルコードの往復](../../references/managed/execute.md#routerとローカルコードの往復)に従ってprepareし、送信する一覧・内容を確認してからsnapshotを指定する。比較元はsnapshotのSHA。collect後、ローカルでの再開が許可されていればimportで新規checkoutへ復元し、差分と報告を確認してQAへ進む。元の作業ツリーには自動適用しない。
@@ -177,7 +177,7 @@ lint 由来の指摘は auto-apply にしない (ルールは構文で判定す
177
177
 
178
178
  1. **修正する** — どう直すかを 1-2 文で提案 (agent が返した `detail` を活用)。承認されたら `Edit` / `Write` で実行
179
179
  2. **このまま許容する** — 妥当な理由があるとき。理由を今回のローカル成果物(implementation.md / 調査・レビューレポート)に残す (後から「なぜ違反のままにしたか」を辿れるように)
180
- 3. **ルールの方を修正する** — 規約自体が現実と乖離している場合。[Shinaps規約の書き方](../../references/rule-writing.md) に従って書き込む全文を作る。新規のときは scope (user / repo_owner / repo) も選ばせる (横断的なら user、この repo 限定なら repo)。同じ文書の「書き込む前に既存の規約と比べる」で比べ (Step 1 の結果はファイルの技術で絞っているので使わない)、結果と全文の差分を示して同意を受けてから shinaps-ai MCP server の `record_rule` (新規) or `update_rule` (既存修正) を実行
180
+ 3. **ルールの方を修正する** — 規約自体が現実と乖離している場合。[Shinaps規約の書き方](../../references/rule-writing.md) に従って書き込む全文を作る。新規のときは scope (user / repo_owner / repo) も選ばせる (横断的なら user、この repo 限定なら repo。repo は [Shinaps規約の取得](../../references/rules.md) の「リポジトリを特定する」で得た `repoId` を添える)。同じ文書の「書き込む前に既存の規約と比べる」で比べ (Step 1 の結果はファイルの技術で絞っているので使わない)、結果と全文の差分を示して同意を受けてから shinaps-ai MCP server の `record_rule` (新規) or `update_rule` (既存修正) を実行
181
181
  4. **後回し** — 別タスクとして扱う。`TaskCreate` で TODO 化し、後の作業セッションで着手できるようにする
182
182
 
183
183
  **提示順は `changed` を先、`pre-existing` を後**。それぞれの中では Violation → Improvement → Question の順。各 finding には `pre-existing` かどうかを明示して提示する (ユーザーが「今回の commit に混ぜるか」を判断する材料になるため)。件数が少なければ 1 回の `AskUserQuestion` に複数 question をまとめてよい (各 finding には前提説明を添える)。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shinaps",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Shinaps AIのCodex向け開発ワークフロー",
5
5
  "skills": "./skills/"
6
6
  }
@@ -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.44.0のskillには同じ版のnpm CLIを導入する(`npm install -g @shinaps/skills@0.44.0`)。pluginだけにはCLIは含まれない。導入・更新が必要ならその権限を確認し、検証のたびに `npx latest` を取得しない。
7
+ 利用中のskillとCLIの版を確認し、0.46.0のskillには同じ版のnpm CLIを導入する(`npm install -g @shinaps/skills@0.46.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
 
@@ -28,7 +28,7 @@ Shinaps AIの規約は、機械検査(`checks`)を持てる。検査は配
28
28
 
29
29
  [Shinaps規約の書き方](rule-writing.md)で書き込む規約に、検査を付けられるかを判断する。
30
30
 
31
- - **使えるルール**: ESLint core、`@typescript-eslint/*`(型情報を使うものを含む)、`vue/*`、同梱の `shinaps/*`(`docs/lint.md` の一覧)。版によって違うので、同じ版のCLIで確かめる。使えるルールが無い規約には検査を付けない(AI照合のまま)。同梱ルールの追加は `docs/lint.md` の手順で別の作業として提案する。
32
- - **coverageの判断**: 規約の全文(scope・labelsを含み、更新では更新後の全文)と、検査の `rule`・`options`・`files` を照らす。`files` の範囲で、規約が求めることへの違反をこのルールが全て報告すると言えるときだけ `full`、見逃しが一つでもありうれば `partial`。lintが規約より広く報告する(例外も報告する)ことは `full` の妨げにならない(指摘はメインが確認する)。例で通ることは根拠にならない。
31
+ - **使えるルール**: ESLint core、`@typescript-eslint/*`(型情報を使うものを含む)、`vue/*`、`react-x/*` のうち `rules-of-hooks`・`exhaustive-deps`・`no-array-index-key`・`no-missing-key`・`no-nested-component-definitions`・`no-unstable-context-value`、`regexp/*`、同梱の `shinaps/*`(`docs/lint.md` の一覧)。版によって違うので、同じ版のCLIで確かめる。使えるルールが無い規約には検査を付けない(AI照合のまま)。同梱ルールの追加は `docs/lint.md` の手順で別の作業として提案する。
32
+ - **coverageの判断**: 規約の全文(scope・labelsを含み、更新では更新後の全文)と、検査の `rule`・`options`・`files` を照らす。`files` の範囲で、規約が求めることへの違反をこのルールが全て報告すると言えるときだけ `full`、見逃しが一つでもありうれば `partial`。lintが規約より広く報告する(例外も報告する)ことは `full` の妨げにならない(指摘はメインが確認する)。例で通ることは根拠にならない。Vueのテンプレートを見るルール(`vue/*` のテンプレート用、`shinaps/vue-no-index-key`)は、HTML以外のテンプレート(`lang="pug"` など)を解析せず報告なしで終わるので、それだけでは `.vue` を対象に `full` にできない。
33
33
  - **補助の確認**: 規約のgood・badの例を一時ディレクトリにファイルとして書き、同じ入口で実行して、badで報告され、goodで報告されないことを確かめる。対象プロジェクトのコードを写さない。
34
- - **書き込み**: `record_rule`・`update_rule` の `checks` に渡す。一括の `record_rules` は `checks` を受け取らない(付けると呼出し全体が入力エラーになる)ので、検査は登録後に `update_rule` で付ける。`update_rule` は配列ごと置換し、`[]` で外す。検査を持つ規約の内容(出典以外)を変える更新では、更新後の全文で検査を見直し、同じ要求で `checks` を渡す。渡さないと `CHECKS_REQUIRED` で保存されない。見直した結果が同じなら同じ配列を渡す。
34
+ - **書き込み**: `record_rule`・`update_rule` の `checks` に渡す。一括の `record_rules` は `checks` を受け取らない(付けると呼出し全体が入力エラーになる)ので、検査は登録後に `update_rule` で付ける。`update_rule` は配列ごと置換し、`[]` で外す。検査を持つ規約の内容(出典とrepoの表示名以外。適用先はscope、repo_ownerのowner、repoのrepoIdで比べる)を変える更新では、更新後の全文で検査を見直し、同じ要求で `checks` を渡す。渡さないと `CHECKS_REQUIRED` で保存されない。見直した結果が同じなら同じ配列を渡す。
@@ -4,7 +4,7 @@ Managed Agents は配布CLI `shinaps-skills managed` で導入・依頼・観測
4
4
 
5
5
  ## アカウント
6
6
 
7
- アカウントは、APIキー1つが属する組織・workspace に利用者が付ける名前で、英小文字・数字と単独のハイフン(`^[a-z0-9]+(-[a-z0-9]+)*$`)にする。同じマシンでは、同じ名前は常に同じ組織・workspace を表すようにする。lock がアカウント名で引かれるので、別の組織に同じ名前を使うと別の組織の lock で適用しようとする(`ant apply` は lock と違う組織・workspace の資格情報を拒否して止まる)。
7
+ アカウントは、APIキー1つが属する組織・workspace に利用者が付ける名前で、先頭は英小文字または数字、以降は英小文字・数字とハイフン(`^[a-z0-9][a-z0-9-]*$`)にする。同じマシンでは、同じ名前は常に同じ組織・workspace を表すようにする。lock がアカウント名で引かれるので、別の組織に同じ名前を使うと別の組織の lock で適用しようとする(`ant apply` は lock と違う組織・workspace の資格情報を拒否して止まる)。
8
8
 
9
9
  ### APIキー
10
10
 
@@ -30,7 +30,7 @@
30
30
  "type": "object",
31
31
  "minProperties": 1,
32
32
  "propertyNames": {
33
- "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
33
+ "pattern": "^[a-z0-9][a-z0-9-]*$"
34
34
  },
35
35
  "additionalProperties": {
36
36
  "type": "object",
@@ -63,7 +63,7 @@ shinaps-skills managed collect --account <name> --session <ID>
63
63
  ```
64
64
 
65
65
  - セッションに紐づく Files(`changes.patch` と作業記録の tar)を、そのアカウントの `sessions/<ID>/outputs/` へ保存し、保存したパスを表示する。同じ名前のファイルは最新のものだけを保存する(lead は毎ターン `changes.patch` を比較元からの全体の差分で置き換える)。名前がファイル名だけでないものは保存せずに止まる。保存したサイズは一覧の値と比べる。
66
- - CLI は差分を適用しない。ローカルの CLI は git を呼ばず、自分では push しない。push と draft PR の作成は、許可した場合にセッション内の lead が行う。`changes.patch` は合意した base の SHA のチェックアウトで `git apply --check` してから、範囲と受入条件に対する差分を確かめる。適用するかは利用者と決める。
66
+ - collect は差分を適用せず、保存だけを行い、自分では push しない。ローカルsnapshotのprepare・importはGitを使い、importは新しいディレクトリにだけ差分を適用する。既存の作業ツリーへは適用しない。push と draft PR の作成は、許可した場合にセッション内の lead が行う。`changes.patch` は合意した base の SHA のチェックアウトで `git apply --check` してから、範囲と受入条件に対する差分を確かめる。適用するかは利用者と決める。
67
67
  - `コミット:` と push 先がある場合は `git fetch origin <head>` で head ブランチを取得し、base の SHA からのコミットと差分を確かめる。
68
68
  - `PR:` がある場合は `gh pr view <PR の URL> --json isDraft,baseRefName,headRefName` または GitHub MCP の読み取りで、draft であること・base と head が合意どおりであることを確かめる。
69
69
 
@@ -74,9 +74,33 @@ shinaps-skills managed collect --account <name> --session <ID>
74
74
  ## 未検証の境界
75
75
 
76
76
  - `managed setup`・`run`・`status`・`reply`・`collect` のコマンド・フラグ・stdin の本文・出力の形は ant 2.4.0 の公式ソースで照合し、偽の `ant` を使うテストで確かめた。実際の `ant` とクラウドではまだ実行していない。
77
- - API の側で決まり、ant のソースでは確かめられないこと: 予算 `max_list_cost.amount` がセント単位か、`mount_path: /CLAUDE.md` が `/mnt/session/uploads/CLAUDE.md` に置かれるか、Files の upload・一覧・取得に `files-api-2025-04-14` と `managed-agents-2026-04-01` の両方の beta を付ければよいか(ant の `--beta` は既定の beta を置き換えるので、全ての呼び出しで明示している)、Files の応答の `filename`・`size_bytes`・`created_at`、events の一覧に役割のスレッドの `agent.message` が混ざるか。lock が無い状態での skill の重複は、同じ setup で作る lead の同名確認で止まる。
77
+ - API の側で決まり、ant のソースでは確かめられないこと: `mount_path: /CLAUDE.md` が `/mnt/session/uploads/CLAUDE.md` に置かれるか、Files の upload・一覧・取得に `files-api-2025-04-14` と `managed-agents-2026-04-01` の両方の beta を付ければよいか(ant の `--beta` は既定の beta を置き換えるので、全ての呼び出しで明示している)、Files の応答の `filename`・`size_bytes`・`created_at`、events の一覧に役割のスレッドの `agent.message` が混ざるか。lock が無い状態での skill の重複は、同じ setup で作る lead の同名確認で止まる。
78
78
  - マウントしたリポジトリの remote へ `git push` できるか(`authorization_token` が push に使われるか)。最初のセッションで最優先に確かめ、できなければ GitHub MCP の push 用ツール(`create_branch`・`push_files`)を有効にするかを利用者と相談する。
79
79
  - commit をチェックアウトしてマウントした作業先から、origin の head ブランチを取得できるか(clone の深さと fetch の資格情報)。
80
80
  - GitHub MCP で draft PR を作れるか。`draft: true` は lead への指示でしか担保されない。
81
81
  - vault に資格情報を登録できたことは、上の3点の確認にならない。
82
82
  - 完了したスレッドが同時実行の枠(25本)を空けるか。ULTRAでは、ラウンドごとに新しく起動する担当を含めて、同時に保つスレッドが多い。
83
+
84
+ ## Routerとローカルコードの往復
85
+
86
+ Claude Account Routerを使う場合は、アプリでAPIキーとクラウド用アカウントを登録し、アプリ内CLIの `cloud managed` 経由で同じmanagedコマンドを呼ぶ。setupとrunはRouterの選択アカウントを使える。status・reply・collect・importは作成結果のaccountとsession_idを明示し、途中の選択変更を引き継がない。キーを取得して会話や設定へ書き出さない。
87
+
88
+ GitHubへpushしていないコードも渡す場合は、先にローカルで固定する。
89
+
90
+ ```sh
91
+ shinaps-skills managed prepare --source /path/to/repository
92
+ ```
93
+
94
+ 戻り値のfilesとmanifestを確認する。追跡中の現在の内容と、Gitで無視されていない新規ファイルを含む。履歴・Git設定・node_modules・.local・.shinapsは含まない。資格情報ファイルやcheckout外へのシンボリックリンクは拒否する。内容の秘密検出は行わないので、送信対象の確認は必要。submoduleはこの方式では扱わない。
95
+
96
+ 合意済みのrunに `--snapshot <manifest>` を付ける。比較元のSHAは保存済みsnapshotから決まり、`--base-sha` は不要。体制・許可・予算・範囲は通常どおり明示する。ローカル方式はpush・draft_prを受理しない。snapshotはFiles APIで送り、GitHubリポジトリのマウントやGitHub tokenを使わない。元のGit履歴は含まれない。
97
+
98
+ collectの後、保存されたセッションを新しいローカルcheckoutへ戻せる。
99
+
100
+ ```sh
101
+ shinaps-skills managed import --account personal --session SESSION_ID --destination /path/to/new-checkout
102
+ ```
103
+
104
+ importはAPIキー不要。保存された送信時点のコード(`~/.local/share/shinaps-ai/skills/snapshots/<SHA>/`。アカウントに属さない。prepareごとに複製とbundleが残るので、importと確認が済んだら削除してよい)を復元し、そのセッションのchanges.patchをindexへ適用する。既存のdestinationや元の作業ツリーは上書きしない。適用失敗時は作成したディレクトリを削除してエラーにする。復元先で差分とreport.mdの報告を読み、ローカルの検証を行って作業を再開する。元のブランチへの統合は別途差分を確認して行う。
105
+
106
+ 実クラウドへの送信・実行・回収は未検証。ローカルの代替antと実Gitでの往復テストは、クラウド環境でのgit利用や成果物生成の保証ではない。予算amountのセント単位は[公式仕様](https://platform.claude.com/docs/en/managed-agents/budgets)で確認済みで、リクエスト途中では停止しないため厳密な請求上限とは異なる。
@@ -60,7 +60,7 @@ coding の規約は、使えるlintのルールで検査できるかを判断し
60
60
 
61
61
  ## scope
62
62
 
63
- 全リポジトリに当てはまる判断だけ `scope: user` にして、他のプロジェクトでも通じる表現で書く。同じownerの配下に共通する判断は `scope: repo_owner`、特定のリポジトリの判断は `scope: repo` にして具体的に書く。
63
+ 全リポジトリに当てはまる判断だけ `scope: user` にして、他のプロジェクトでも通じる表現で書く。同じownerの配下に共通する判断は `scope: repo_owner`、特定のリポジトリの判断は `scope: repo` にして具体的に書く。`scope: repo` で書くには、[Shinaps規約の取得](rules.md)の「リポジトリを特定する」でrepositoryの `id` を得る。
64
64
 
65
65
  ## 目安と上限
66
66
 
@@ -25,12 +25,25 @@ Shinaps AIのMCPから規約を取得する手順。取得は呼出し元(メ
25
25
 
26
26
  対象が確定していない工程(devの入口・`spec`・`explore`・`debug`)は、論点の技術を語彙から推定してよい。`filterUserRulesByLabels` は指定しない。
27
27
 
28
+ ## リポジトリを特定する
29
+
30
+ `scope: repo` の規約はGitHubのrepositoryの`id`で識別し、owner・名前は表示にだけ使う。名前は改名で変わり、旧名は別のrepositoryやforkが使えるので、名前の一致を同じrepositoryの根拠にしない。
31
+
32
+ 1. 対象のownerと名前を決める。既定は現在のGit workspaceの `git remote get-url origin`(`github.com/<owner>/<repo>(.git)?` の形)。登録先が別のrepositoryなら、その値にする。この形に当たらなければ(GitHub以外のremoteなど)、`repoOwner`・`repoName`・`repoId` のどれも渡さない。
33
+ 2. `gh api repos/<owner>/<repo> --jq '{id, full_name}'` を1回呼ぶ。改名前の名前でもGitHubは改名後のrepositoryを返すので、`id` を `repoId`、`full_name` の `/` の前後を `repoOwner`・`repoName` にする。
34
+ 3. 呼出しが失敗したら(`gh` が無い、認証が無い、404など。理由は区別しない)、`repoId`・`repoName` は渡さない。1で決めたownerだけを `repoOwner` にする。
35
+
36
+ - **取得**: `search_rules` には `repoOwner` と `repoId` を渡し、`repoName` は渡さない(入力エラーになる)。`repoId` が無いときは `scope: repo` の規約が返らないので、「リポジトリを特定できず、このrepositoryの規約は取得していない」と1文で報告する。
37
+ - **新規の書込み**: `scope: repo` の `record_rule`・`record_rules` には `repoOwner`・`repoName`・`repoId` をそろえて渡す。
38
+ - **更新**: `update_rule` で `scope: "repo"` と3つをそろえて渡すのは、適用先を repo にするとき、別のrepositoryに変えるとき、表示名を現在の名前にするとき(2の `full_name` が規約の `repoOwner`・`repoName` と違うとき)だけ。内容だけの更新では適用先の項目を渡さない(`scope` なしで渡すと入力エラーになる)。
39
+ - **`repoId` が得られないとき**: `scope: repo` では書き込まず、`scope: user`・`scope: repo_owner` に変えるか中止するかを利用者に確認する。範囲を変えるときは、[Shinaps規約の書き方](rule-writing.md)の比較をやり直す。
40
+
28
41
  ## 一覧を取る
29
42
 
30
- `search_rules` を `fields: "summary"` で呼ぶ。`repoOwner`・`repoName` は現在のGit workspaceから渡す。対象が確定した照合では `filterUserRulesByLabels: true` を渡す。
43
+ `search_rules` を `fields: "summary"` で呼ぶ。`repoOwner`・`repoId` は「リポジトリを特定する」で決めた値を渡す。対象が確定した照合では `filterUserRulesByLabels: true` を渡す。
31
44
 
32
45
  ```json
33
- { "fields": "summary", "repoOwner": "...", "repoName": "...", "languages": ["typescript", "javascript"], "platforms": ["cloudflare-workers"], "frameworks": ["hono"], "filterUserRulesByLabels": true }
46
+ { "fields": "summary", "repoOwner": "...", "repoId": 123456789, "languages": ["typescript", "javascript"], "platforms": ["cloudflare-workers"], "frameworks": ["hono"], "filterUserRulesByLabels": true }
34
47
  ```
35
48
 
36
49
  - サーバーは同じ軸をOR、指定した軸の間をANDで判定する。
@@ -60,7 +73,7 @@ Shinaps AIのMCPから規約を取得する手順。取得は呼出し元(メ
60
73
 
61
74
  [Shinaps規約の書き方](rule-writing.md)の比較に使う規約は、次の条件で取得する。
62
75
 
63
- - `search_rules` は `fields: "summary"` で全ページ取得し、`filterUserRulesByLabels` と `kind` を渡さない。`repoOwner`・`repoName` は、登録先のowner・repoがあればその値、無い分は現在のGit workspaceの値にする。
76
+ - `search_rules` は `fields: "summary"` で全ページ取得し、`filterUserRulesByLabels` と `kind` を渡さない。`repoOwner`・`repoId` は「リポジトリを特定する」で決めた値にする。
64
77
  - 公式規約も一覧に載るよう、`list_search_facets` の語彙が8件以内の技術軸(`languages` など)を一つ選び、その全slugを渡す。技術軸はその軸のlabelが無い公式規約にも一致する。`topics` は一致必須で別のtopicの公式規約が返らなくなるので渡さない。渡せる軸が無ければ公式規約の取得は未完了とする。
65
78
  - `projectVersions` には確定した版だけを渡す。渡しても渡さなくても、条件に合わない条件付きの公式規約は返らない。
66
79
  - 一覧のtitleとlabelから同じ場面を扱う可能性を否定できない規約を選び、「全文を取る」で全文を取得する。文字列の一致で選ばない(公式規約のtitleは英語)。「全文を取る規約を選ぶ」は対象ファイルとの照合の基準で、ここには適用しない。
@@ -10,7 +10,7 @@ description: GitHubの過去PRレビューから繰り返し適用できる規
10
10
  1. `gh`で期間内のマージ済みPRを取得する。ページングし、並び順と日付フィルターの関係を守る。更新日時順の一覧で古いマージ日時を一件見つけても、それ以降の取得を打ち切らない。
11
11
  2. PRのreview本文・inline comment・conversation commentを取得する。PR descriptionはレビュー指摘として扱わない。APIのBot種別は除外し、判断が必要な投稿者は確認する。認証情報やリポジトリ全体をサービスへ送信しない。
12
12
  3. 複数のレビューで繰り返され、適用条件とWHYが説明できる規約を候補にする。根拠のない例・URLを作らず、レビューコメントの出典を付ける。
13
- 4. 候補を[Shinaps規約の書き方](../../references/rule-writing.md)に従って書き、同じ文書の「書き込む前に既存の規約と比べる」で候補ごとに結果を決める。提示の候補はまとまりの終わりに示し、残りの投入は止めない。新規の候補だけを対象repoのスコープへ、実際のMCPツールのスキーマに従って登録する。応答は入力の位置とidだけなので、重複して投入されなかった候補は入力からtitleを得て、上書きせず提示へ進める。全プロジェクト共通へ広げる場合は利用者の判断を確認し、書き直した全文で比べ直す。既存の規約への更新の候補は差分を提示して許可を得る。`RULE_TOO_LARGE`はその呼出し分が全体として保存されなかったことを示すので、details の limit・actual・titleを確かめ、該当候補を短くしてやり直す。更新の`RULE_UPDATE_CONFLICT`は全文を読み直して再試行する。`RULE_LIMIT_REACHED`は残りの枠が足りずその呼出し分が全体として保存されなかったことを示すので、一部だけを登録する手順へ切り替えずに止め、details の limit・actualを示して、件数を絞るか既存ルールを整理するかを利用者に確認する。それより前の呼出しで投入した分は保存されている。以降のまとまりの抽出・投入にも進まず、利用者の選択を待つ。
13
+ 4. 候補を[Shinaps規約の書き方](../../references/rule-writing.md)に従って書き、同じ文書の「書き込む前に既存の規約と比べる」で候補ごとに結果を決める。提示の候補はまとまりの終わりに示し、残りの投入は止めない。新規の候補だけを対象repoのスコープへ、実際のMCPツールのスキーマに従って登録する。対象repoは[Shinaps規約の取得](../../references/rules.md)の「リポジトリを特定する」でrepositoryの`id`を得て指定し、得られなければrepoのスコープでは登録せず利用者に確認する。応答は入力の位置とidだけなので、重複して投入されなかった候補は入力からtitleを得て、上書きせず提示へ進める。全プロジェクト共通へ広げる場合は利用者の判断を確認し、書き直した全文で比べ直す。既存の規約への更新の候補は差分を提示して許可を得る。`RULE_TOO_LARGE`はその呼出し分が全体として保存されなかったことを示すので、details の limit・actual・titleを確かめ、該当候補を短くしてやり直す。更新の`RULE_UPDATE_CONFLICT`は全文を読み直して再試行する。`RULE_LIMIT_REACHED`は残りの枠が足りずその呼出し分が全体として保存されなかったことを示すので、一部だけを登録する手順へ切り替えずに止め、details の limit・actualを示して、件数を絞るか既存ルールを整理するかを利用者に確認する。それより前の呼出しで投入した分は保存されている。以降のまとまりの抽出・投入にも進まず、利用者の選択を待つ。
14
14
  5. 大量処理は100 PR程度のまとまりで結果を報告し、継続範囲を確認する。結果が不明な保存失敗では再投入の前に読み戻し、重複を避ける。`RATE_LIMITED`は制限の時間枠が切り替わるまで待ってから1回だけ再試行し、続けて拒否されたら中断して報告する。
15
15
 
16
16
  終了時は処理したPR・コメント数、新規登録・更新・書かない・提示・除外・未処理の件数と根拠を示す。取得・保存が部分的ならその範囲を明記する。記録する場合は.shinaps/work/を使い、[作業記録の共通手順](../../references/work-records.md)に従って初期化・保全する。
@@ -9,10 +9,16 @@ description: Claude Managed Agents の lead へ、アカウントを選んで de
9
9
 
10
10
  # Managed Agents へ依頼する
11
11
 
12
- [設定とアカウント](../../references/managed/configuration.md)と[依頼と観測の手順](../../references/managed/execute.md)を読んで進める。操作は配布CLIの `managed run`・`status`・`reply`・`collect` で行う。
12
+ [設定とアカウント](../../references/managed/configuration.md)と[依頼と観測の手順](../../references/managed/execute.md)を読んで進める。操作は配布CLIの `managed prepare`・`run`・`status`・`reply`・`collect`・`import` で行う。
13
13
 
14
14
  - 呼び出しのたびにアカウント・base ブランチと SHA・head ブランチ・範囲と受入条件・体制(LITE・BASIC・ULTRA)・許可・予算を利用者と合意し、引数で明示する。設定・lock・過去の作業から推定しない。
15
15
  - 設定(`.shinaps/managed.json`)が無いか旧版なら、手順の「設定を用意する」に従って一問ずつ確認して作る。設定から読むのは `repository` とアカウントの `vault_id` だけで、lead と環境の ID はアカウントの lock から読む。lock が無ければ止め、導入は managed-setup で行う。
16
16
  - 計画・実装・レビューの工程と追加の担当は lead に任せ、ここでは指示しない。
17
- - 回収した差分は適用しない。確認と適用の判断は利用者と行う。push・merge・deploy・release・Issue の更新・セッションのアーカイブは行わない。Linear の進行とは独立した入口である。
17
+ - 回収した差分を既存の作業ツリーへ適用しない(importは新しいディレクトリにだけ復元する)。確認と統合の判断は利用者と行う。push・merge・deploy・release・Issue の更新・セッションのアーカイブは行わない。Linear の進行とは独立した入口である。
18
18
  - キーと token は利用者が環境変数に設定する。受け取らない・表示しない・記録しない。
19
+
20
+ ## Routerとローカルでの再開
21
+
22
+ 利用者がClaude Account Routerとの連携を選んだ場合は、アプリ内CLIの `cloud managed` を使う。APIキーを読み出して会話へ返さない。作成結果のaccountとsession_idを保存し、後続操作でその組を使う。
23
+
24
+ 未コミットのローカルコードを渡す依頼では、[ローカルコードの往復](../../references/managed/execute.md#routerとローカルコードの往復)に従ってprepareし、送信する一覧・内容を確認してからsnapshotを指定する。比較元はsnapshotのSHA。collect後、ローカルでの再開が許可されていればimportで新規checkoutへ復元し、差分と報告を確認してQAへ進む。元の作業ツリーには自動適用しない。