ai-commons 0.2.10 → 0.2.12
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 +48 -61
- package/RULES.md +5 -0
- package/hooks/ensure-rules-pointer.js +9 -20
- package/package.json +2 -2
- package/scripts/installer.js +13 -31
- package/scripts/managed-block.js +27 -21
- package/scripts/targets.js +124 -0
- package/AGENTS.md +0 -81
package/README.md
CHANGED
|
@@ -1,78 +1,61 @@
|
|
|
1
1
|
# ai-commons
|
|
2
2
|
|
|
3
|
-
Общие правила для
|
|
4
|
-
npm
|
|
5
|
-
|
|
6
|
-
- В корневом `CLAUDE.md` — управляемый блок между метками:
|
|
7
|
-
```
|
|
8
|
-
<!-- ai-commons:begin -->
|
|
9
|
-
... @node_modules/ai-commons/AGENTS.md ...
|
|
10
|
-
<!-- ai-commons:end -->
|
|
11
|
-
```
|
|
12
|
-
Этот блок **обновляется хуком** при каждом старте сессии. Всё, что в файле
|
|
13
|
-
снаружи блока, — твоё, не трогается. Если `CLAUDE.md` не было — создастся
|
|
14
|
-
с одним блоком.
|
|
15
|
-
- Хуки в `.claude/settings.json`:
|
|
16
|
-
- `SessionStart` — обновляет блок в `CLAUDE.md` на каждом старте сессии.
|
|
17
|
-
- `Stop` и `SessionEnd` — сохраняют диалог в `.ai-dialogs/YYMMDD-HHII-<topic>.md`.
|
|
18
|
-
- Запись `.ai-dialogs/` в `.gitignore`.
|
|
3
|
+
Общие правила для AI-агентов + автосохранение Claude-диалогов. Поставляется
|
|
4
|
+
как npm-зависимость и поддерживает четыре популярных таргета:
|
|
19
5
|
|
|
20
|
-
|
|
6
|
+
| Агент / стандарт | Файл в потребителе | Режим |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| Claude Code | `CLAUDE.md` | управляемый блок (`@AGENTS.md` import) |
|
|
9
|
+
| OpenAI Codex / стандартный AGENTS.md | `AGENTS.md` | управляемый блок (встроенное содержимое) |
|
|
10
|
+
| Cursor | `.cursor/rules/ai-commons.mdc` | весь файл принадлежит ai-commons |
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
12
|
+
В файлах с управляемым блоком (`CLAUDE.md`, `AGENTS.md`) содержимое между
|
|
13
|
+
метками `<!-- ai-commons:begin -->` и `<!-- ai-commons:end -->`
|
|
14
|
+
перезаписывается; всё остальное в файле — твоё, не трогается.
|
|
26
15
|
|
|
27
|
-
|
|
16
|
+
Дополнительно для Claude:
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
- `SessionStart`-хук обновляет все три target-файла на каждом старте сессии.
|
|
19
|
+
- `Stop` / `SessionEnd`-хук сохраняет диалог в
|
|
20
|
+
`.ai-dialogs/YYMMDD-HHII-<topic>.md`.
|
|
21
|
+
- `.ai-dialogs/` добавляется в `.gitignore`.
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
## Установка в новый проект
|
|
34
24
|
|
|
35
25
|
```bash
|
|
36
|
-
|
|
26
|
+
cd my-new-project
|
|
27
|
+
npm init -y # если package.json ещё нет
|
|
28
|
+
npm i -D /Users/os/okneigres-repos/ai-commons
|
|
37
29
|
```
|
|
38
30
|
|
|
39
|
-
|
|
31
|
+
`postinstall` всё настраивает автоматически. Запустить вручную:
|
|
40
32
|
|
|
41
33
|
```bash
|
|
42
|
-
npx ai-commons
|
|
43
|
-
#
|
|
44
|
-
npm uninstall ai-commons
|
|
34
|
+
npx ai-commons install # идемпотентно: можно гонять сколько угодно раз
|
|
35
|
+
npx ai-commons uninstall # снять блоки, хуки и Cursor-файл
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
нём есть другое содержимое — удаляется только если оказался пустым) и
|
|
49
|
-
вычистит хуки `ai-commons` из `.claude/settings.json`, не трогая чужие хуки.
|
|
38
|
+
## Правила
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- **MUST** — обязательно, игнорировать нельзя ни при каких обстоятельствах
|
|
56
|
-
(например: не пушить force в main, не коммитить секреты).
|
|
57
|
-
- **Желательно** — стандарт по умолчанию, отступать можно только с явной
|
|
58
|
-
причиной (например: краткие ответы, без избыточных абстракций).
|
|
59
|
-
- **Остальное** — стиль и привычки (например: `curl` одной строкой,
|
|
60
|
-
ссылки на код в формате `path:line`).
|
|
40
|
+
Лежат в [`RULES.md`](RULES.md) — это публичный контракт, его содержимое
|
|
41
|
+
`targets.js` копирует во все target-файлы потребителей. `AGENTS.md` в
|
|
42
|
+
этом репо — заметки для работы над самим `ai-commons`, в потребителей
|
|
43
|
+
не попадает.
|
|
61
44
|
|
|
62
45
|
## Как обновить правила во всех проектах
|
|
63
46
|
|
|
64
|
-
1. Правишь `
|
|
47
|
+
1. Правишь `RULES.md` здесь.
|
|
65
48
|
2. `git commit && git push` (или просто коммит, если репо локальный).
|
|
66
|
-
3. В каждом потребителе:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
3. В каждом потребителе:
|
|
50
|
+
- **Claude**: ничего делать не надо — `SessionStart`-хук подтянет новые
|
|
51
|
+
правила в следующей сессии (для `file:`-зависимостей) или после
|
|
52
|
+
`npm update ai-commons` (для опубликованных).
|
|
53
|
+
- **Codex / Cursor**: `npm install` (или `npx ai-commons install`) —
|
|
54
|
+
перепишет блоки и Cursor-файл.
|
|
72
55
|
|
|
73
|
-
## Сохранение диалогов
|
|
56
|
+
## Сохранение диалогов (только Claude)
|
|
74
57
|
|
|
75
|
-
После каждого Claude Stop (и при завершении сессии) хук пишет
|
|
58
|
+
После каждого Claude `Stop` (и при завершении сессии) хук пишет
|
|
76
59
|
`.ai-dialogs/YYMMDD-HHII-<topic>.md`:
|
|
77
60
|
|
|
78
61
|
- Timestamp — момент первой реплики пользователя в сессии.
|
|
@@ -89,15 +72,17 @@ npm uninstall ai-commons
|
|
|
89
72
|
|
|
90
73
|
```
|
|
91
74
|
ai-commons/
|
|
92
|
-
├──
|
|
75
|
+
├── RULES.md — источник правды (правила), копируется в потребителей
|
|
76
|
+
├── AGENTS.md — заметки для работы над самим ai-commons
|
|
93
77
|
├── package.json — bin + postinstall
|
|
94
78
|
├── bin/ai-commons.js — CLI: install / uninstall
|
|
95
79
|
├── scripts/
|
|
96
80
|
│ ├── postinstall.js — npm-хук, запускает installer.install
|
|
97
|
-
│ ├── installer.js —
|
|
98
|
-
│
|
|
81
|
+
│ ├── installer.js — установка/удаление в потребителе
|
|
82
|
+
│ ├── targets.js — список таргет-файлов (Claude/Codex/Cursor)
|
|
83
|
+
│ └── managed-block.js — вставка/обновление блока в markdown
|
|
99
84
|
└── hooks/
|
|
100
|
-
├── ensure-rules-pointer.js — SessionStart-хук
|
|
85
|
+
├── ensure-rules-pointer.js — SessionStart-хук (рефреш всех таргетов)
|
|
101
86
|
└── save-dialog.js — Stop / SessionEnd-хук
|
|
102
87
|
```
|
|
103
88
|
|
|
@@ -107,7 +92,9 @@ ai-commons/
|
|
|
107
92
|
|
|
108
93
|
## Что не делается
|
|
109
94
|
|
|
110
|
-
- `
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
- `permissions` и `env` из `settings.json` потребителя не трогаются — только
|
|
96
|
+
свой набор хуков (SessionStart / Stop / SessionEnd) мержится.
|
|
97
|
+
- Старый формат Cursor (`.cursorrules` в корне) не поддерживается — только
|
|
98
|
+
современный `.cursor/rules/*.mdc`.
|
|
99
|
+
- Автосейв диалогов реализован только для Claude (у других агентов нет
|
|
100
|
+
аналогичных hook'ов).
|
package/RULES.md
ADDED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// SessionStart hook:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// SessionStart hook: re-applies ai-commons rule files in the consumer
|
|
3
|
+
// project. This refreshes the managed block in CLAUDE.md / AGENTS.md and
|
|
4
|
+
// the full .cursor/rules/ai-commons.mdc file using the latest content from
|
|
5
|
+
// node_modules/ai-commons/RULES.md.
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
|
-
const
|
|
9
|
-
const { applyBlock } = require('../scripts/managed-block');
|
|
10
|
-
|
|
11
|
-
const POINTER_FILE = 'CLAUDE.md';
|
|
8
|
+
const { applyAllTargets } = require('../scripts/targets');
|
|
12
9
|
|
|
13
10
|
function readStdinJson() {
|
|
14
11
|
try {
|
|
@@ -23,22 +20,14 @@ function main() {
|
|
|
23
20
|
const input = readStdinJson();
|
|
24
21
|
const cwd = input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
25
22
|
if (!cwd || !fs.existsSync(cwd)) {
|
|
26
|
-
process.stderr.write('[ai-commons] no cwd, skipping
|
|
23
|
+
process.stderr.write('[ai-commons] no cwd, skipping refresh\n');
|
|
27
24
|
process.exit(0);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
: null;
|
|
34
|
-
const next = applyBlock(existing);
|
|
35
|
-
|
|
36
|
-
if (existing != null && next === existing) {
|
|
37
|
-
process.exit(0);
|
|
27
|
+
const results = applyAllTargets(cwd);
|
|
28
|
+
for (const { target, result } of results) {
|
|
29
|
+
process.stderr.write(`[ai-commons] ${target.id}: ${target.file} ${result}\n`);
|
|
38
30
|
}
|
|
39
|
-
|
|
40
|
-
fs.writeFileSync(pointerPath, next);
|
|
41
|
-
process.stderr.write(`[ai-commons] refreshed block in ${POINTER_FILE}\n`);
|
|
42
31
|
process.exit(0);
|
|
43
32
|
}
|
|
44
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-commons",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Shared Claude Code rules + dialog-saver hook for projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-commons": "bin/ai-commons.js"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"bin",
|
|
14
14
|
"scripts",
|
|
15
15
|
"hooks",
|
|
16
|
-
"
|
|
16
|
+
"RULES.md",
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"engines": {
|
package/scripts/installer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Installs ai-commons into a consumer project:
|
|
2
|
-
// - writes/refreshes
|
|
2
|
+
// - writes/refreshes rule files for Claude / Codex / Cursor (see targets.js)
|
|
3
3
|
// - merges our hooks into .claude/settings.json (preserving any existing hooks)
|
|
4
4
|
// - ensures .ai-dialogs/ is gitignored
|
|
5
5
|
//
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
-
const {
|
|
11
|
+
const { applyAllTargets, removeAllTargets } = require('./targets');
|
|
12
12
|
|
|
13
13
|
const PACKAGE_NAME = require('../package.json').name;
|
|
14
|
-
const POINTER_FILE = 'CLAUDE.md';
|
|
15
14
|
const SETTINGS_FILE = path.join('.claude', 'settings.json');
|
|
16
15
|
const GITIGNORE_FILE = '.gitignore';
|
|
17
16
|
|
|
@@ -39,20 +38,11 @@ function readJsonOrEmpty(p) {
|
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
function
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const next = applyBlock(existing);
|
|
48
|
-
if (existing != null && next === existing) return false;
|
|
49
|
-
fs.writeFileSync(pointerPath, next);
|
|
50
|
-
log(
|
|
51
|
-
existing == null
|
|
52
|
-
? `created ${POINTER_FILE} with ai-commons block`
|
|
53
|
-
: `refreshed ai-commons block in ${POINTER_FILE}`
|
|
54
|
-
);
|
|
55
|
-
return true;
|
|
41
|
+
function writeRuleFiles(consumerRoot) {
|
|
42
|
+
const results = applyAllTargets(consumerRoot);
|
|
43
|
+
for (const { target, result } of results) {
|
|
44
|
+
log(`${target.id}: ${target.file} ${result}`);
|
|
45
|
+
}
|
|
56
46
|
}
|
|
57
47
|
|
|
58
48
|
function stripManagedHooksFromEvent(eventEntries) {
|
|
@@ -101,26 +91,18 @@ function install(consumerRoot) {
|
|
|
101
91
|
if (!consumerRoot || !fs.existsSync(consumerRoot)) {
|
|
102
92
|
throw new Error(`Invalid consumer root: ${consumerRoot}`);
|
|
103
93
|
}
|
|
104
|
-
|
|
94
|
+
writeRuleFiles(consumerRoot);
|
|
105
95
|
mergeHooks(consumerRoot);
|
|
106
96
|
ensureGitignore(consumerRoot);
|
|
107
97
|
log('install complete');
|
|
108
98
|
}
|
|
109
99
|
|
|
110
100
|
function uninstall(consumerRoot) {
|
|
111
|
-
// Strip our managed
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const next = removeBlock(existing);
|
|
117
|
-
if (next.trim() === '') {
|
|
118
|
-
fs.rmSync(pointerPath);
|
|
119
|
-
log(`removed empty ${POINTER_FILE}`);
|
|
120
|
-
} else if (next !== existing) {
|
|
121
|
-
fs.writeFileSync(pointerPath, next);
|
|
122
|
-
log(`removed ai-commons block from ${POINTER_FILE}`);
|
|
123
|
-
}
|
|
101
|
+
// Strip our managed content from each target file. Leave user content
|
|
102
|
+
// intact; remove block-mode files only if they end up empty.
|
|
103
|
+
const results = removeAllTargets(consumerRoot);
|
|
104
|
+
for (const { target, result } of results) {
|
|
105
|
+
log(`${target.id}: ${target.file} ${result}`);
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
// Strip our hooks from settings.
|
package/scripts/managed-block.js
CHANGED
|
@@ -1,44 +1,50 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Helpers for inserting/updating an ai-commons block inside a host markdown
|
|
2
|
+
// file (CLAUDE.md, AGENTS.md, etc.). The block content is passed in by the
|
|
3
|
+
// caller, so this module is target-agnostic.
|
|
3
4
|
|
|
4
|
-
const PACKAGE_NAME = 'ai-commons';
|
|
5
5
|
const BEGIN = '<!-- ai-commons:begin -->';
|
|
6
6
|
const END = '<!-- ai-commons:end -->';
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
'<!--
|
|
11
|
-
'<!--
|
|
12
|
-
'<!-- in the ai-commons repo. Everything OUTSIDE these markers is yours. -->',
|
|
13
|
-
`@node_modules/${PACKAGE_NAME}/AGENTS.md`,
|
|
14
|
-
END,
|
|
8
|
+
const HEADER_COMMENTS = [
|
|
9
|
+
'<!-- Managed by ai-commons. Content between these markers is auto-generated. -->',
|
|
10
|
+
'<!-- To change rules, edit RULES.md in the ai-commons repo and re-run -->',
|
|
11
|
+
'<!-- `npm install` in this project (Claude users get auto-refresh on session). -->',
|
|
15
12
|
].join('\n');
|
|
16
13
|
|
|
17
14
|
function escapeRegex(s) {
|
|
18
15
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
// Greedy on purpose: if marker strings somehow appear inside the rendered
|
|
19
|
+
// body, we still match the OUTERMOST begin..end pair instead of stopping
|
|
20
|
+
// at the first nested-looking end.
|
|
21
|
+
const BLOCK_RE = new RegExp(`${escapeRegex(BEGIN)}[\\s\\S]*${escapeRegex(END)}`);
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
function buildBlock(innerContent) {
|
|
24
|
+
return `${BEGIN}\n${HEADER_COMMENTS}\n\n${innerContent}\n${END}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Returns the host content with the managed block inserted/refreshed.
|
|
24
28
|
// - If the host already contains markers, replace content between them.
|
|
25
29
|
// - Otherwise, prepend the block at the top, separated by a blank line.
|
|
26
30
|
// - If host is null/undefined, return a file containing just the block.
|
|
27
|
-
function applyBlock(hostContent) {
|
|
28
|
-
|
|
31
|
+
function applyBlock(hostContent, innerContent) {
|
|
32
|
+
const block = buildBlock(innerContent);
|
|
33
|
+
if (hostContent == null) return block + '\n';
|
|
29
34
|
if (BLOCK_RE.test(hostContent)) {
|
|
30
|
-
return hostContent.replace(BLOCK_RE,
|
|
35
|
+
return hostContent.replace(BLOCK_RE, block);
|
|
31
36
|
}
|
|
32
|
-
const sep =
|
|
33
|
-
|
|
37
|
+
const sep =
|
|
38
|
+
hostContent.length === 0 || hostContent.startsWith('\n') ? '\n' : '\n\n';
|
|
39
|
+
return block + sep + hostContent;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
// Returns the host content with the managed block (and surrounding blank
|
|
37
|
-
// lines) removed. Safe to call when no block is present.
|
|
38
42
|
function removeBlock(hostContent) {
|
|
39
43
|
if (!hostContent) return '';
|
|
40
|
-
const re = new RegExp(
|
|
44
|
+
const re = new RegExp(
|
|
45
|
+
`${escapeRegex(BEGIN)}[\\s\\S]*${escapeRegex(END)}\\n*`
|
|
46
|
+
);
|
|
41
47
|
return hostContent.replace(re, '');
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
module.exports = { applyBlock, removeBlock, BEGIN, END
|
|
50
|
+
module.exports = { applyBlock, removeBlock, BEGIN, END };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Defines every file ai-commons writes into a consumer project, and how to
|
|
2
|
+
// render its content. Currently covers Claude, Codex (root AGENTS.md), and
|
|
3
|
+
// Cursor (.cursor/rules/ai-commons.mdc).
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { applyBlock, removeBlock } = require('./managed-block');
|
|
8
|
+
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
function readRules() {
|
|
12
|
+
return fs.readFileSync(path.join(PACKAGE_ROOT, 'RULES.md'), 'utf8').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Each target:
|
|
16
|
+
// id — internal name for logs
|
|
17
|
+
// file — path relative to consumer root
|
|
18
|
+
// mode — 'block' (manage section between markers) | 'fullFile' (own whole file)
|
|
19
|
+
// render(r) — render the block body / file body from the rules text
|
|
20
|
+
//
|
|
21
|
+
// Block mode preserves user content outside the markers.
|
|
22
|
+
// fullFile mode means we own the entire file (created/removed as a unit).
|
|
23
|
+
|
|
24
|
+
const TARGETS = [
|
|
25
|
+
{
|
|
26
|
+
id: 'claude',
|
|
27
|
+
file: 'CLAUDE.md',
|
|
28
|
+
mode: 'block',
|
|
29
|
+
// Claude supports @-imports natively; defer to the local AGENTS.md
|
|
30
|
+
// (which carries the embedded rules), avoiding duplication.
|
|
31
|
+
render: () => '@AGENTS.md',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'codex+standard',
|
|
35
|
+
file: 'AGENTS.md',
|
|
36
|
+
mode: 'block',
|
|
37
|
+
// Codex CLI and the cross-tool AGENTS.md standard read this file
|
|
38
|
+
// directly — embed the full rules text.
|
|
39
|
+
render: (rules) => rules,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cursor',
|
|
43
|
+
file: path.join('.cursor', 'rules', 'ai-commons.mdc'),
|
|
44
|
+
mode: 'fullFile',
|
|
45
|
+
// Cursor expects MDC: YAML frontmatter + markdown body.
|
|
46
|
+
render: (rules) =>
|
|
47
|
+
`---\ndescription: ai-commons shared rules\nalwaysApply: true\n---\n\n${rules}\n`,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function applyTarget(consumerRoot, target, rules) {
|
|
52
|
+
const fullPath = path.join(consumerRoot, target.file);
|
|
53
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
54
|
+
|
|
55
|
+
if (target.mode === 'fullFile') {
|
|
56
|
+
const next = target.render(rules);
|
|
57
|
+
const existing = fs.existsSync(fullPath)
|
|
58
|
+
? fs.readFileSync(fullPath, 'utf8')
|
|
59
|
+
: null;
|
|
60
|
+
if (existing === next) return null;
|
|
61
|
+
fs.writeFileSync(fullPath, next);
|
|
62
|
+
return existing == null ? 'created' : 'refreshed';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// block mode
|
|
66
|
+
const existing = fs.existsSync(fullPath)
|
|
67
|
+
? fs.readFileSync(fullPath, 'utf8')
|
|
68
|
+
: null;
|
|
69
|
+
const next = applyBlock(existing, target.render(rules));
|
|
70
|
+
if (existing != null && next === existing) return null;
|
|
71
|
+
fs.writeFileSync(fullPath, next);
|
|
72
|
+
return existing == null ? 'created' : 'refreshed';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeTarget(consumerRoot, target) {
|
|
76
|
+
const fullPath = path.join(consumerRoot, target.file);
|
|
77
|
+
if (!fs.existsSync(fullPath)) return null;
|
|
78
|
+
|
|
79
|
+
if (target.mode === 'fullFile') {
|
|
80
|
+
fs.rmSync(fullPath);
|
|
81
|
+
return 'removed';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const existing = fs.readFileSync(fullPath, 'utf8');
|
|
85
|
+
const next = removeBlock(existing);
|
|
86
|
+
if (next.trim() === '') {
|
|
87
|
+
fs.rmSync(fullPath);
|
|
88
|
+
return 'removed-empty';
|
|
89
|
+
}
|
|
90
|
+
if (next !== existing) {
|
|
91
|
+
fs.writeFileSync(fullPath, next);
|
|
92
|
+
return 'block-removed';
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function applyAllTargets(consumerRoot) {
|
|
98
|
+
const rules = readRules();
|
|
99
|
+
const results = [];
|
|
100
|
+
for (const target of TARGETS) {
|
|
101
|
+
const result = applyTarget(consumerRoot, target, rules);
|
|
102
|
+
if (result) results.push({ target, result });
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function removeAllTargets(consumerRoot) {
|
|
108
|
+
const results = [];
|
|
109
|
+
for (const target of TARGETS) {
|
|
110
|
+
const result = removeTarget(consumerRoot, target);
|
|
111
|
+
if (result) results.push({ target, result });
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
TARGETS,
|
|
118
|
+
PACKAGE_ROOT,
|
|
119
|
+
readRules,
|
|
120
|
+
applyTarget,
|
|
121
|
+
removeTarget,
|
|
122
|
+
applyAllTargets,
|
|
123
|
+
removeAllTargets,
|
|
124
|
+
};
|
package/AGENTS.md
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# ai-commons — общие правила для всех проектов
|
|
2
|
-
|
|
3
|
-
Этот файл подгружается в каждую Claude Code сессию проекта-потребителя через
|
|
4
|
-
импорт `@node_modules/ai-commons/AGENTS.md` в корневом `CLAUDE.md`. В корневом
|
|
5
|
-
`CLAUDE.md` потребителя есть управляемая секция между метками
|
|
6
|
-
`<!-- ai-commons:begin -->` и `<!-- ai-commons:end -->` — её содержимое
|
|
7
|
-
перезаписывается хуком `ai-commons` на каждом старте сессии. **Не редактируй
|
|
8
|
-
блок между метками** — изменения будут потеряны. Всё, что в `CLAUDE.md`
|
|
9
|
-
снаружи блока, остаётся нетронутым: пиши там свои проектные правила.
|
|
10
|
-
|
|
11
|
-
Чтобы поменять общие правила, правь этот файл в репозитории `ai-commons`.
|
|
12
|
-
|
|
13
|
-
Правила сгруппированы по трём уровням приоритета. Игнорировать MUST нельзя
|
|
14
|
-
ни при каких обстоятельствах. Желательно — стандартное поведение по
|
|
15
|
-
умолчанию, отступление возможно только с явной причиной. Остальное —
|
|
16
|
-
стиль и привычки.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## MUST — обязательно, игнорировать нельзя
|
|
21
|
-
|
|
22
|
-
- **Никогда не делай `git push --force` в `main`/`master`.** Если нужно
|
|
23
|
-
переписать историю, спроси разрешения и работай через feature-ветку.
|
|
24
|
-
- **Никогда не коммить секреты** (`.env`, ключи, токены, credentials.json
|
|
25
|
-
и т.п.). Если такой файл попал в `git add`, удали его из стейджа и
|
|
26
|
-
предупреди пользователя.
|
|
27
|
-
- **Не запускай деструктивные операции без подтверждения**: `rm -rf`,
|
|
28
|
-
`git reset --hard`, `DROP TABLE`, `git clean -fd`, отключение pre-commit
|
|
29
|
-
хуков (`--no-verify`), force push куда угодно.
|
|
30
|
-
- **Не правь блок между метками `ai-commons:begin / end`** в корневом
|
|
31
|
-
`CLAUDE.md` потребителя — он управляется `ai-commons` и перезаписывается
|
|
32
|
-
на старте сессии. Свои правила пиши снаружи этого блока.
|
|
33
|
-
- **Для групп связанных проектов используй `git-coherent`**
|
|
34
|
-
(`npm i -g git-coherent`, бинари `git-coherent` / `gitc`) — синхронизация
|
|
35
|
-
git-операций по сиблинг-репозиториям, которые опт-инятся через
|
|
36
|
-
`.git-coherent` маркер. Это «среднее между monorepo и микросервисами»:
|
|
37
|
-
каждый подпроект остаётся отдельным git-репо, `git-coherent` запускает
|
|
38
|
-
операции над всеми отмеченными сразу. Не предлагай и не создавай
|
|
39
|
-
монорепо или независимые multi-repo схемы для таких задач без явного
|
|
40
|
-
запроса.
|
|
41
|
-
- **Не отключай и не модифицируй хуки `ai-commons`** в `.claude/settings.json`
|
|
42
|
-
без явного запроса пользователя.
|
|
43
|
-
|
|
44
|
-
## Желательно — стандарт по умолчанию, отступать только с причиной
|
|
45
|
-
|
|
46
|
-
- Отвечай кратко: один-два абзаца, без избыточных вводных и итогов.
|
|
47
|
-
- Перед нетривиальной задачей дай короткий план (3–5 пунктов), потом
|
|
48
|
-
делай. Не пиши длинные эссе про подход.
|
|
49
|
-
- Не добавляй фич, рефакторингов и абстракций сверх задачи. Bug fix не
|
|
50
|
-
тянет за собой cleanup.
|
|
51
|
-
- Не пиши комментарии, описывающие *что* делает код. Пиши только если
|
|
52
|
-
нужно объяснить *почему* — и только если это неочевидно.
|
|
53
|
-
- Не валидируй то, что не может случиться. Доверяй внутренним
|
|
54
|
-
гарантиям типов и фреймворков; валидируй только на границах системы
|
|
55
|
-
(вход пользователя, внешние API).
|
|
56
|
-
- Перед удалением «непонятного» файла/ветки/конфигурации — выясни, не
|
|
57
|
-
работа ли это пользователя в процессе. Спроси, если есть сомнения.
|
|
58
|
-
|
|
59
|
-
## Остальное — стиль и привычки
|
|
60
|
-
|
|
61
|
-
- `curl`-примеры — одной строкой, без переносов `\`.
|
|
62
|
-
- При ссылках на код используй формат `path/to/file.ts:42`.
|
|
63
|
-
- Для UI-задач — запусти dev-сервер и проверь в браузере перед тем, как
|
|
64
|
-
говорить «готово». Тесты и типы проверяют корректность кода, не
|
|
65
|
-
фичи.
|
|
66
|
-
- Если задача неясная или формулировка двусмысленная — задай один-два
|
|
67
|
-
уточняющих вопроса вместо угадывания.
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Как это устроено технически
|
|
72
|
-
|
|
73
|
-
- `ai-commons` ставится как обычная npm-зависимость:
|
|
74
|
-
`npm i -D ai-commons` (или `file:~/okneigres-repos/ai-commons` для локальной разработки).
|
|
75
|
-
- `postinstall` вставляет/обновляет блок между метками `ai-commons:begin/end`
|
|
76
|
-
в корневом `CLAUDE.md` потребителя. Если файла нет — создаёт его.
|
|
77
|
-
- `SessionStart`-хук `ai-commons` обновляет этот блок на каждом старте
|
|
78
|
-
сессии — это гарантирует, что правила всегда актуальны. Остальное
|
|
79
|
-
содержимое `CLAUDE.md` не трогается.
|
|
80
|
-
- `Stop`-хук сохраняет каждый диалог в
|
|
81
|
-
`.ai-dialogs/YYMMDD-HHII-<topic>.md` (см. README модуля).
|