@spec-box/sdd 0.8.0 → 0.9.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 +23 -1
- package/assets/project/wiki.README.md +30 -0
- package/assets/roles/distiller.md +1 -0
- package/assets/roles/implementer.md +2 -1
- package/assets/roles/planner.md +4 -3
- package/assets/roles/reviewer.md +3 -2
- package/assets/roles/tester.md +2 -1
- package/assets/roles/verifier.md +2 -1
- package/assets/skills/sbox-run.md +1 -1
- package/assets/skills/sbox-wiki.md +21 -0
- package/bin/sbox-wiki.js +5 -0
- package/dist/adapters/host/index.js +1 -1
- package/dist/adapters/host/index.js.map +1 -1
- package/dist/cli/commands/log.js +45 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/protocol.js +5 -7
- package/dist/cli/commands/protocol.js.map +1 -1
- package/dist/cli/main.js +2 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/core/change.js +4 -1
- package/dist/core/change.js.map +1 -1
- package/dist/core/log.js +62 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/packet.js +5 -0
- package/dist/core/packet.js.map +1 -1
- package/dist/core/report.js +6 -0
- package/dist/core/report.js.map +1 -1
- package/dist/core/wiki.js +10 -70
- package/dist/core/wiki.js.map +1 -1
- package/dist/wiki/cli.js +60 -0
- package/dist/wiki/cli.js.map +1 -0
- package/dist/wiki/markdown.js +41 -0
- package/dist/wiki/markdown.js.map +1 -0
- package/dist/wiki/store.js +268 -0
- package/dist/wiki/store.js.map +1 -0
- package/docs/design.md +27 -4
- package/package.json +5 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
import fg from 'fast-glob';
|
|
6
|
+
import { parseDoc } from '../core/project-docs.js';
|
|
7
|
+
import { SboxError } from '../core/errors.js';
|
|
8
|
+
import { diag } from '../core/diagnostics.js';
|
|
9
|
+
import { markdown } from './markdown.js';
|
|
10
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
11
|
+
const list = (value) => typeof value === 'string' ? [value] : Array.isArray(value) ? value.filter((x) => typeof x === 'string') : [];
|
|
12
|
+
export const pageSummary = ({ body, text, links, invalid, ...page }) => page;
|
|
13
|
+
export function parsePage(file, content) {
|
|
14
|
+
const doc = parseDoc(content);
|
|
15
|
+
const fm = doc.frontmatter;
|
|
16
|
+
const parsed = markdown(doc.body);
|
|
17
|
+
const invalid = Boolean(fm.__invalid) || !fm || typeof fm !== 'object' || Array.isArray(fm)
|
|
18
|
+
|| ['id', 'summary', 'title'].some(key => fm[key] !== undefined && typeof fm[key] !== 'string')
|
|
19
|
+
|| ['read_when', 'source_roots', 'areas'].some(key => fm[key] !== undefined && typeof fm[key] !== 'string' && !(Array.isArray(fm[key]) && fm[key].every((v) => typeof v === 'string')));
|
|
20
|
+
return {
|
|
21
|
+
file, id: typeof fm.id === 'string' ? fm.id : file.replace(/\.md$/i, ''),
|
|
22
|
+
title: typeof fm.title === 'string' ? fm.title : parsed.headings[0]?.title ?? file,
|
|
23
|
+
summary: typeof fm.summary === 'string' ? fm.summary : '', read_when: list(fm.read_when), source_roots: list(fm.source_roots), areas: list(fm.areas),
|
|
24
|
+
revision: hash(content), ...parsed, body: doc.body, invalid,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Markdown остаётся истиной. Кеш содержит текст для поиска, но index отдаёт только карточки. */
|
|
28
|
+
export class WikiStore {
|
|
29
|
+
projectRoot;
|
|
30
|
+
dir;
|
|
31
|
+
cacheFile;
|
|
32
|
+
constructor(dir, projectRoot = dir) {
|
|
33
|
+
this.projectRoot = projectRoot;
|
|
34
|
+
this.dir = path.resolve(dir);
|
|
35
|
+
this.cacheFile = path.join(os.tmpdir(), `sbox-wiki-${process.getuid?.() ?? 'user'}`, `${hash(this.dir)}.json`);
|
|
36
|
+
}
|
|
37
|
+
safe(file) {
|
|
38
|
+
const target = path.resolve(this.dir, file);
|
|
39
|
+
if (target === this.dir || !target.startsWith(this.dir + path.sep))
|
|
40
|
+
throw new SboxError('WIKI_PATH', `Путь вне wiki: ${file}`);
|
|
41
|
+
// Не следуем симлинкам ни при чтении, ни при записи.
|
|
42
|
+
let current = target;
|
|
43
|
+
for (;;) {
|
|
44
|
+
try {
|
|
45
|
+
if (fs.lstatSync(current).isSymbolicLink())
|
|
46
|
+
throw new SboxError('WIKI_PATH', `Символическая ссылка: ${current}`);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (e.code !== 'ENOENT')
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
if (current === this.dir)
|
|
53
|
+
break;
|
|
54
|
+
current = path.dirname(current);
|
|
55
|
+
}
|
|
56
|
+
return target;
|
|
57
|
+
}
|
|
58
|
+
pages(rebuild = false) {
|
|
59
|
+
if (!fs.existsSync(this.dir))
|
|
60
|
+
return [];
|
|
61
|
+
let cache = { version: 1, entries: {} };
|
|
62
|
+
if (!rebuild) {
|
|
63
|
+
try {
|
|
64
|
+
const value = JSON.parse(fs.readFileSync(this.cacheFile, 'utf8'));
|
|
65
|
+
if (value.version === 1 && value.entries)
|
|
66
|
+
cache = value;
|
|
67
|
+
}
|
|
68
|
+
catch { /* восстанавливаемый кеш */ }
|
|
69
|
+
}
|
|
70
|
+
const entries = {};
|
|
71
|
+
for (const file of fg.sync('**/*.md', { cwd: this.dir, onlyFiles: true, followSymbolicLinks: false }).sort()) {
|
|
72
|
+
const target = this.safe(file);
|
|
73
|
+
const stat = fs.statSync(target);
|
|
74
|
+
const stamp = `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}:${stat.ino}`;
|
|
75
|
+
const cached = cache.entries[file];
|
|
76
|
+
const valid = cached?.page && ['id', 'title', 'summary', 'revision', 'body', 'text'].every(key => typeof cached.page[key] === 'string')
|
|
77
|
+
&& ['read_when', 'source_roots', 'areas', 'links'].every(key => { const value = cached.page[key]; return Array.isArray(value) && value.every(x => typeof x === 'string'); })
|
|
78
|
+
&& Array.isArray(cached.page.headings) && cached.page.headings.every(h => h && typeof h.anchor === 'string' && typeof h.title === 'string' && Number.isInteger(h.start) && Number.isInteger(h.end) && Number.isInteger(h.level))
|
|
79
|
+
&& cached.page.file === file && typeof cached.page.invalid === 'boolean';
|
|
80
|
+
entries[file] = cached?.stamp === stamp && valid ? cached : { stamp, page: parsePage(file, fs.readFileSync(target, 'utf8')) };
|
|
81
|
+
}
|
|
82
|
+
// Отсутствие доступа к кешу не должно мешать читать wiki.
|
|
83
|
+
try {
|
|
84
|
+
fs.mkdirSync(path.dirname(this.cacheFile), { recursive: true, mode: 0o700 });
|
|
85
|
+
const temp = `${this.cacheFile}.${randomUUID()}`;
|
|
86
|
+
try {
|
|
87
|
+
fs.writeFileSync(temp, JSON.stringify({ version: 1, entries }), { mode: 0o600 });
|
|
88
|
+
fs.renameSync(temp, this.cacheFile);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
fs.rmSync(temp, { force: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { /* кеш необязателен */ }
|
|
95
|
+
return Object.values(entries).map(e => e.page);
|
|
96
|
+
}
|
|
97
|
+
resolve(ref, pages = this.pages()) {
|
|
98
|
+
const byFile = pages.find(p => p.file === ref);
|
|
99
|
+
if (byFile)
|
|
100
|
+
return byFile;
|
|
101
|
+
const matches = pages.filter(p => p.id === ref);
|
|
102
|
+
if (matches.length !== 1)
|
|
103
|
+
throw new SboxError(matches.length ? 'WIKI_AMBIGUOUS' : 'WIKI_NOT_FOUND', `Страница «${ref}»: найдено ${matches.length}. Используйте уникальный id или путь .md.`);
|
|
104
|
+
return matches[0];
|
|
105
|
+
}
|
|
106
|
+
get(ref, section) {
|
|
107
|
+
const page = this.resolve(ref);
|
|
108
|
+
const content = fs.readFileSync(this.safe(page.file), 'utf8');
|
|
109
|
+
const fresh = parsePage(page.file, content);
|
|
110
|
+
if (!section)
|
|
111
|
+
return { ...pageSummary(fresh), content };
|
|
112
|
+
const matches = fresh.headings.filter(h => h.anchor === section || h.title === section);
|
|
113
|
+
if (matches.length !== 1)
|
|
114
|
+
throw new SboxError('WIKI_SECTION', `Раздел «${section}»: найдено ${matches.length}. Используйте якорь из index/get.`);
|
|
115
|
+
const h = matches[0];
|
|
116
|
+
return { ...pageSummary(fresh), content: fresh.body.split('\n').slice(h.start, h.end).join('\n') };
|
|
117
|
+
}
|
|
118
|
+
link(page, href) {
|
|
119
|
+
if (/^(?:[a-z][\w+.-]*:|\/\/)/i.test(href))
|
|
120
|
+
return null;
|
|
121
|
+
const split = href.indexOf('#');
|
|
122
|
+
try {
|
|
123
|
+
const file = decodeURIComponent((split < 0 ? href : href.slice(0, split)).split('?')[0]);
|
|
124
|
+
const anchor = decodeURIComponent(split < 0 ? '' : href.slice(split + 1));
|
|
125
|
+
const target = file ? path.resolve(this.dir, path.dirname(page.file), file) : path.join(this.dir, page.file);
|
|
126
|
+
const relative = path.relative(this.dir, target).split(path.sep).join('/');
|
|
127
|
+
this.safe(relative);
|
|
128
|
+
return { file: relative, anchor };
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new SboxError('WIKI_LINK_PATH', `Недопустимая ссылка: ${href}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
validate(pages = this.pages()) {
|
|
135
|
+
const out = [];
|
|
136
|
+
const ids = new Set();
|
|
137
|
+
for (const p of pages) {
|
|
138
|
+
const add = (severity, code, message) => out.push(diag(severity, code, message, p.file));
|
|
139
|
+
if (p.invalid)
|
|
140
|
+
add('error', 'WIKI_METADATA', 'Некорректный YAML или тип поля метаданных');
|
|
141
|
+
if (!p.id.trim() || ids.has(p.id))
|
|
142
|
+
add('error', 'WIKI_ID', `Пустой или повторяющийся id: ${p.id}`);
|
|
143
|
+
ids.add(p.id);
|
|
144
|
+
if (!p.summary.trim())
|
|
145
|
+
add('warning', 'WIKI_SUMMARY', 'У страницы нет summary');
|
|
146
|
+
if (!p.read_when.some(x => x.trim()))
|
|
147
|
+
add('warning', 'WIKI_READ_WHEN', 'У страницы нет read_when');
|
|
148
|
+
for (const root of p.source_roots)
|
|
149
|
+
if (!fs.existsSync(path.resolve(this.projectRoot, root)))
|
|
150
|
+
add('warning', 'WIKI_SOURCE_ROOT', `Не существует source_roots: ${root}`);
|
|
151
|
+
for (const href of p.links) {
|
|
152
|
+
try {
|
|
153
|
+
const link = this.link(p, href);
|
|
154
|
+
if (!link)
|
|
155
|
+
continue;
|
|
156
|
+
const dest = pages.find(x => x.file === link.file);
|
|
157
|
+
if (!dest && (!fs.existsSync(this.safe(link.file)) || /\.md$/i.test(link.file)))
|
|
158
|
+
add('error', 'WIKI_LINK_MISSING', `Не найдена цель: ${href}`);
|
|
159
|
+
else if (dest && link.anchor && !dest.headings.some(h => h.anchor === link.anchor))
|
|
160
|
+
add('error', 'WIKI_LINK_ANCHOR', `Не найден раздел: ${href}`);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
add('error', 'WIKI_LINK_PATH', e.message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
backlinks(ref) {
|
|
170
|
+
const pages = this.pages();
|
|
171
|
+
const target = this.resolve(ref, pages);
|
|
172
|
+
return pages.flatMap(p => p.links.flatMap(href => {
|
|
173
|
+
try {
|
|
174
|
+
return this.link(p, href)?.file === target.file ? [{ id: p.id, file: p.file, href }] : [];
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
search(query, sourcePath, limit = 5) {
|
|
182
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
183
|
+
throw new SboxError('WIKI_LIMIT', 'limit должен быть целым от 1 до 100');
|
|
184
|
+
const words = [...new Set(query.toLocaleLowerCase().match(/[\p{L}\p{N}_-]+/gu) ?? [])];
|
|
185
|
+
if (!words.length && !sourcePath)
|
|
186
|
+
throw new SboxError('WIKI_QUERY', 'Укажите запрос или --path');
|
|
187
|
+
const within = (a, b) => a === b || a.startsWith(b.replace(/\/$/, '') + '/');
|
|
188
|
+
return this.pages().map(p => {
|
|
189
|
+
const reasons = [];
|
|
190
|
+
let score = 0;
|
|
191
|
+
const fields = [['title', p.title, 5], ['summary', p.summary, 4], ['read_when', p.read_when.join(' '), 4], ['areas', p.areas.join(' '), 3], ['text', p.text, 1]];
|
|
192
|
+
for (const [name, value, weight] of fields) {
|
|
193
|
+
const found = words.filter(w => value.toLocaleLowerCase().includes(w));
|
|
194
|
+
if (found.length) {
|
|
195
|
+
score += found.length * weight;
|
|
196
|
+
reasons.push(`${name}: ${found.join(', ')}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (sourcePath && p.source_roots.some(r => within(sourcePath, r) || within(r, sourcePath))) {
|
|
200
|
+
score += 10;
|
|
201
|
+
reasons.push(`source_roots: ${sourcePath}`);
|
|
202
|
+
}
|
|
203
|
+
const lines = p.text.split('\n');
|
|
204
|
+
const snippet = (lines.find(line => words.some(w => line.toLocaleLowerCase().includes(w))) ?? p.summary).slice(0, 300);
|
|
205
|
+
return { ...pageSummary(p), score, reasons, snippet };
|
|
206
|
+
}).filter(p => p.score > 0).sort((a, b) => b.score - a.score || a.file.localeCompare(b.file)).slice(0, limit);
|
|
207
|
+
}
|
|
208
|
+
put(ref, content, options) {
|
|
209
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
210
|
+
const lock = this.safe('.sbox-wiki.lock');
|
|
211
|
+
let fd;
|
|
212
|
+
try {
|
|
213
|
+
fd = fs.openSync(lock, 'wx', 0o600);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
if (e.code !== 'EEXIST')
|
|
217
|
+
throw e;
|
|
218
|
+
throw new SboxError('WIKI_BUSY', 'Другая запись wiki активна. После аварии удалите .sbox-wiki.lock, убедившись, что процесс завершён.');
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
fs.writeSync(fd, String(process.pid));
|
|
222
|
+
const pages = this.pages(true);
|
|
223
|
+
let file;
|
|
224
|
+
if (options.create) {
|
|
225
|
+
if (options.ifMatch)
|
|
226
|
+
throw new SboxError('WIKI_ARGUMENT', '--create несовместим с --if-match');
|
|
227
|
+
file = path.relative(this.dir, this.safe(ref)).split(path.sep).join('/');
|
|
228
|
+
if (!file.endsWith('.md'))
|
|
229
|
+
throw new SboxError('WIKI_PATH', 'Для новой страницы укажите относительный путь .md');
|
|
230
|
+
if (fs.existsSync(this.safe(file)))
|
|
231
|
+
throw new SboxError('WIKI_EXISTS', `Страница уже существует: ${file}`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const current = this.resolve(ref, pages);
|
|
235
|
+
file = current.file;
|
|
236
|
+
if (!options.ifMatch || current.revision !== options.ifMatch)
|
|
237
|
+
throw new SboxError('WIKI_CONFLICT', 'Нужна текущая revision из get в --if-match. Перечитайте страницу и примените правку к новой версии.');
|
|
238
|
+
}
|
|
239
|
+
const candidate = parsePage(file, content);
|
|
240
|
+
const diagnostics = this.validate([...pages.filter(p => p.file !== file), candidate]);
|
|
241
|
+
if (diagnostics.some(d => d.severity === 'error'))
|
|
242
|
+
throw new SboxError('WIKI_INVALID', JSON.stringify(diagnostics));
|
|
243
|
+
const target = this.safe(file);
|
|
244
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
245
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
246
|
+
try {
|
|
247
|
+
fs.writeFileSync(temp, content, { flag: 'wx', mode: fs.existsSync(target) ? fs.statSync(target).mode : 0o644 });
|
|
248
|
+
// Повторная проверка также ловит правку обычным редактором во время валидации.
|
|
249
|
+
if (!options.create && hash(fs.readFileSync(target, 'utf8')) !== options.ifMatch)
|
|
250
|
+
throw new SboxError('WIKI_CONFLICT', 'Страница изменилась во время записи');
|
|
251
|
+
if (options.create) {
|
|
252
|
+
fs.linkSync(temp, target);
|
|
253
|
+
}
|
|
254
|
+
else
|
|
255
|
+
fs.renameSync(temp, target);
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
fs.rmSync(temp, { force: true });
|
|
259
|
+
}
|
|
260
|
+
return { ...pageSummary(candidate), diagnostics };
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
fs.closeSync(fd);
|
|
264
|
+
fs.rmSync(lock, { force: true });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/wiki/store.ts"],"names":[],"mappings":"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"}
|
package/docs/design.md
CHANGED
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
|
|
131
131
|
Независимо от профиля инструмент останавливается на вопросе приоритета P0: без ответа нельзя писать спецификации и тесты. Вопросы P1 останавливают только на включённом гейте `plan`, иначе принимается рекомендованный вариант и записывается как допущение. Вопросы P2 не останавливают никогда.
|
|
132
132
|
|
|
133
|
-
На гейте `proposal` CLI показывает расхождения (`conflicts`): противоречия запросу из Evidence Pack и отступления proposal с решениями планировщика. Человек видит, где план отклонился от его слов, до утверждения, а оркестратор хоста показывает список дословно, не пересказывая сообщения субагентов.
|
|
133
|
+
На гейте `proposal` CLI показывает расхождения (`conflicts`): противоречия запросу из Evidence Pack и отступления proposal с решениями планировщика. Человек видит, где план отклонился от его слов, до утверждения, а оркестратор хоста показывает список дословно, не пересказывая сообщения субагентов. Вопросы из последнего ответа планировщика CLI хранит в `change.yaml` (`questions`) и печатает на гейте рядом с расхождениями с командой ответа (`answer`: `sbox approve plan --answer Q1=<вариант>`); следующий ответ планировщика заменяет список.
|
|
134
134
|
|
|
135
135
|
Канал гейта настраивается в конфиге списком `gates.channels`. Реализованные каналы: `cli` (команда `sbox approve <change> <gate> [--comment]`), `pr-comments` (комментарий `/sbox approve plan` в пул-реквесте, читается адаптером репозитория), `tracker` (комментарий или статус в тикете через адаптер трекера), `chat` (вопрос в сессии в interactive-режиме). Черновой пул-реквест создаётся на фазе propose только если включён канал `pr-comments`.
|
|
136
136
|
|
|
@@ -388,7 +388,7 @@ runs:
|
|
|
388
388
|
|
|
389
389
|
Файл `changeset.json` после фазы implement содержит список изменённых путей относительно базовой ветки и дайджест дифа. Верификатор и ревьюер получают этот дайджест в пакете, а их вердикты записываются в `reviewed_digest`. Перед доставкой CLI пересчитывает дайджест и при расхождении возвращает изменение в фазу verify.
|
|
390
390
|
|
|
391
|
-
Файл `log.md` хранит тегированные однострочные записи, как в sarah: `[TASK]` для событий изменения, `[CODE]` для проверенных фактов о коде, `[RULE]` для принятых правил, `[HUMAN]` для предпочтений сессии. Записи `[CODE]` и `[RULE]` после мержа переносятся в wiki проекта ролью distiller.
|
|
391
|
+
Файл `log.md` хранит тегированные однострочные записи, как в sarah: `[TASK]` для событий изменения, `[CODE]` для проверенных фактов о коде, `[RULE]` для принятых правил, `[HUMAN]` для предпочтений сессии. Записи добавляет команда `sbox log add --tag CODE|RULE|TASK|HUMAN "<текст>"`: одна строка `- [TAG] дата автор: текст` не длиннее 300 знаков, автор по умолчанию роль и номер выданного пакета. Журнал есть в каждом пакете (`files.log`), и роли читают его перед работой: запись `[CODE]` сильнее факта из evidence или раннего артефакта, если она появилась позже; так поправка планировщика к evidence доходит до реализатора, а не остаётся в его ответе. Противоречие утверждённому артефакту (proposal после гейта, дельты, design) оформляется блокером категории «артефакт», а не записью. CLI не пишет `[TASK]` автоматически: события запусков уже есть в `change.yaml`, а журнал остаётся сигналом для ролей и distiller. Записи `[CODE]` и `[RULE]` после мержа переносятся в wiki проекта ролью distiller.
|
|
392
392
|
|
|
393
393
|
## 7. Знания проекта
|
|
394
394
|
|
|
@@ -463,7 +463,7 @@ runs:
|
|
|
463
463
|
|
|
464
464
|
Папка `.sbox/wiki/` хранит долговечные знания о коде, которые не помещаются в категории выше: механизмы, инварианты, подводные камни по областям. Страницы имеют тот же фронтматтер, роутер `sbox route --path | --query` возвращает план чтения из одной-двух страниц, линтер проверяет структуру. Роль distiller после мержа переносит записи `[CODE]` и `[RULE]` из `log.md` в wiki по правилам допуска: запись должна отвечать на повторяющийся вопрос и предотвращать класс ошибок, а не пересказывать один файл.
|
|
465
465
|
|
|
466
|
-
|
|
466
|
+
Индекс страниц (summary, read_when, source_roots) входит в пакет каждой роли, роль читает страницы с подходящим read_when. Отдельный инструмент `sbox-wiki` предоставляет индекс, лексический поиск с учётом путей, чтение и запись страниц, обратные ссылки и валидацию (раздел 16). Роутер с бюджетом контекста и дистилляция остаются расширениями.
|
|
467
467
|
|
|
468
468
|
### Метрики решателя
|
|
469
469
|
|
|
@@ -780,7 +780,7 @@ interface TestReportAdapter {
|
|
|
780
780
|
| События | `events poll`, `events publish` | Опрос и публикация событий координации |
|
|
781
781
|
| Доставка | `deliver [--check] [--keep-draft]`, `gates poll` | Чеклист готовности и доставка: intent, архив, коммит с ключом, push, пул-реквест; опрос гейтов в комментариях пул-реквеста |
|
|
782
782
|
| Среда | `ci install --target github \| docker` | Шаблоны workflows GitHub Actions (запуск @spec-box/sdd и тесты с JSON-отчётом) и Dockerfile |
|
|
783
|
-
| Знания | `route`, `distill`, `wiki lint` | План чтения wiki, дистилляция журнала, проверка wiki |
|
|
783
|
+
| Знания | `route`, `distill`, `wiki lint`, `log add`, `log show` | План чтения wiki, дистилляция журнала, проверка wiki, записи журнала изменения |
|
|
784
784
|
| Браузер | `sbox-browser install \| installed \| uninstall \| doctor \| login \| start \| stop \| status \| state \| goto \| snapshot \| click …` | Отдельный бинарник: управляемый Chrome для проверки интерфейса агентами и людьми, см. раздел 11 |
|
|
785
785
|
|
|
786
786
|
Все команды поддерживают `--json` и выводят ровно один JSON-документ в stdout. Диагностика в едином конверте: `severity`, `code`, `message`, `target`, `fix`. Человекочитаемый вывод идёт в stderr. Контракт взят из OpenSpec, потому что агенты уже умеют с ним работать.
|
|
@@ -851,3 +851,26 @@ packages/
|
|
|
851
851
|
| 6. Расширения | Схема `bugfix` с причинным контрактом и RED-оракулом, wiki и дистилляция, шина событий для Arcadia (адаптер OpenSpec реализован досрочно) | Порядок определяют потребности пилотов |
|
|
852
852
|
|
|
853
853
|
Каждый этап заканчивается изменением в реальном проекте, а не демонстрацией на образце.
|
|
854
|
+
|
|
855
|
+
## 16. Отдельный инструмент wiki (реализовано)
|
|
856
|
+
|
|
857
|
+
`sbox-wiki` — отдельный бинарник в пакете, по аналогии с `sbox-browser`. Модуль `src/wiki` не требует состояния изменения или инициализации SDD: `--dir` выбирает произвольную папку Markdown, `--cwd` задаёт рабочий каталог. Без `--dir` используется `project.wiki` ближайшего проекта SDD, иначе `wiki/` в рабочем каталоге. Адаптер `src/core/wiki.ts` использует этот же модуль для пакетов ролей и диагностики `sbox doctor`.
|
|
858
|
+
|
|
859
|
+
### Контракт
|
|
860
|
+
|
|
861
|
+
- `index [--rebuild]` возвращает карточки всех Markdown-страниц, включая README: id (по умолчанию относительный путь без `.md`), заголовок, summary, read_when, source_roots, areas, revision и заголовки разделов с якорями. Текст страниц не включается. В пакетах SDD сохраняется прежнее исключение README.
|
|
862
|
+
- `search [query] [--path путь] [--limit N]` ищет без учёта регистра по тексту, заголовку, summary, read_when и areas. Совпадения в метаданных имеют больший вес; пересечение с source_roots повышает позицию. Возвращаются причины, оценка и фрагмент до 300 символов. Поиск лексический, без embeddings, морфологии или автоматического подбора синонимов; --path не является строгим фильтром. Лимит — от 1 до 100, по умолчанию 5.
|
|
863
|
+
- `get <id|путь.md> [--section заголовок|якорь]` возвращает полный Markdown с фронтматтером или выбранный раздел с вложенными подразделами. JSON всегда содержит SHA-256 revision всей страницы; для обновления нужно прочитать полную страницу.
|
|
864
|
+
- `put <id|путь.md> --file файл --if-match revision` сохраняет полную страницу, `--file -` читает stdin. Новая страница создаётся по относительному пути `.md` с `--create`. Устаревшая или отсутствующая revision отклоняется. Записи инструмента сериализуются блокировкой папки; файл заменяется атомарно. Правки сторонним редактором проверяются повторно перед заменой, но редактор не участвует в блокировке: полной транзакционной гарантии против произвольного внешнего процесса нет.
|
|
865
|
+
- `backlinks <id|путь.md>` возвращает входящие ссылки, включая ссылки на разделы.
|
|
866
|
+
- `validate` проверяет типы метаданных, уникальность непустых id, относительные Markdown-ссылки и якоря. summary/read_when и отсутствующие source_roots дают предупреждения. Битые ссылки, ссылки за пределы wiki, дубликаты id и некорректные метаданные дают ошибки и код выхода 1. put проверяет всю будущую wiki до записи: удаление заголовка с входящими ссылками запрещено. Для согласованной правки нескольких страниц используются обычные файлы с последующим validate.
|
|
867
|
+
|
|
868
|
+
Ссылки разбираются Markdown-парсером (markdown-it), включая reference links; кодовые примеры не образуют рёбра. Якорь строится из текста заголовка: нижний регистр, буквы/цифры/комбинируемые знаки/дефисы/подчёркивания, пробелы заменены дефисами; повторы получают суффикс `-1`, `-2`. Поддерживаются ATX и Setext. Пользовательские HTML-якоря и HTML-ссылки не поддерживаются. Локальные изображения и вложения проверяются на существование, якоря проверяются для Markdown-страниц. Внешние URL не загружаются. Символические ссылки исключаются из обхода, чтение/запись через них запрещены.
|
|
869
|
+
|
|
870
|
+
### Индекс и авторство
|
|
871
|
+
|
|
872
|
+
Markdown остаётся источником истины. Кеш хранится в системной временной папке пользователя, вне репозитория, с правами каталога 0700 и файла 0600. Содержит метаданные, разобранные ссылки, заголовки и текст для поиска. При каждом обращении сравниваются список файлов и stat (mtime, ctime, размер, inode); перечитываются только изменённые страницы, удалённые исчезают из индекса. Кеш можно удалить или пересобрать `index --rebuild`; отсутствие права записать кеш не препятствует чтению.
|
|
873
|
+
|
|
874
|
+
summary и read_when пишет автор или агент: CLI не генерирует смысловые описания. При обновлении текста автор пересматривает метаданные. revision — версия содержимого, а не свидетельство истинности; автоматическое определение устаревания по коду, семантический поиск и дистилляция остаются расширениями этапа 6.
|
|
875
|
+
|
|
876
|
+
Все команды поддерживают `--json`: результат `{ok:true,data:...}`, ошибка команды `{ok:false,error:{code,message}}`; validate возвращает диагностики в data и код выхода 1 при ошибках. Скилл `assets/skills/sbox-wiki.md` устанавливается обоим хостам и подключается всем ролям; право записи ограничивается полномочиями роли. Wiki-шаблон хранится в `assets/project/wiki.README.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spec-box/sdd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "@spec-box/sdd: автономная реализация продуктовых фич ИИ-агентами",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
16
|
"sbox": "bin/sbox.js",
|
|
17
|
-
"sbox-browser": "bin/sbox-browser.js"
|
|
17
|
+
"sbox-browser": "bin/sbox-browser.js",
|
|
18
|
+
"sbox-wiki": "bin/sbox-wiki.js"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"bin",
|
|
@@ -40,12 +41,14 @@
|
|
|
40
41
|
"@puppeteer/browsers": "^3.2.3",
|
|
41
42
|
"commander": "^15.0.0",
|
|
42
43
|
"fast-glob": "^3.3.3",
|
|
44
|
+
"markdown-it": "^15.0.2",
|
|
43
45
|
"picomatch": "^4.0.7",
|
|
44
46
|
"puppeteer-core": "^25.12.0",
|
|
45
47
|
"yaml": "^2.9.0",
|
|
46
48
|
"zod": "^4.6.2"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
51
|
+
"@types/markdown-it": "^14.2.0",
|
|
49
52
|
"@types/node": "^22.20.2",
|
|
50
53
|
"@types/picomatch": "^4.0.3",
|
|
51
54
|
"tsx": "^4.23.13",
|