@shahimat/ai-core 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  Свежее сверху. Одна версия — список новых команд и проверок.
4
4
 
5
+ ## 0.1.1
6
+
7
+ - Дельта, попавшая в коммит после сбоя сессии, закрывается: пункт
8
+ засчитывается, если цель изменена в коммитах с появления дельты;
9
+ `delta check` предупреждает о закоммиченной дельте.
10
+ - `delta check --name` и `delta close --name`: нарушения в чужих дельтах
11
+ — предупреждение со списком файлов, а не провал.
12
+ - Дельта не прошла схему — ошибка схемы идёт первой, производные
13
+ нарушения «без revise» по её целям свёрнуты в одну строку.
14
+ - peer `vitest` — `>=3`: проекты на vitest 3 ставятся без ERESOLVE.
15
+
5
16
  ## 0.1.0
6
17
 
7
18
  - `delta close --name <дельта>` закрывает только эту дельту: чужая
package/README.md CHANGED
@@ -89,9 +89,13 @@ Mutation — `context index`, `adapters sync`, `delta close`,
89
89
  - `ai delta check | close` — дельта проектной документации в
90
90
  `context/.delta/*.yml` (пункты `add` / `refine` / `revise`). `check`
91
91
  сверяет слои документации с `HEAD`: удалённое или изменённое знание
92
- без пункта `revise` — нарушение. `close` после приёмки (check чист,
93
- пункты выполнены) удаляет дельту и печатает строки `revise` для тела
94
- коммита.
92
+ без пункта `revise` — нарушение; дельта не прошла схему — её ошибка
93
+ первой, производные нарушения по её целям свёрнуты. `close` после
94
+ приёмки (check чист, пункты выполнены) удаляет дельту и печатает
95
+ строки `revise` для тела коммита. `--name <дельта>` сужает обе
96
+ команды до одной дельты: нарушения чужих — предупреждение. Дельта,
97
+ попавшая в коммит, — предупреждение; её пункт засчитывается, если цель
98
+ изменена в коммитах с появления дельты.
95
99
  - `ai role run <name> --file <задание> [--out <ответ>]` — роль из
96
100
  `.agents/agents/<name>.md` (в источнике — `agents/<name>.md`)
97
101
  headless: текст задания — на stdin, итог — сообщением протокола в
@@ -91,6 +91,7 @@ export interface DeltaResult {
91
91
  revisions: Revision[];
92
92
  changed: Set<string>;
93
93
  diagnostics: Diagnostic[];
94
+ warnings: Diagnostic[];
94
95
  accepted: Accepted[];
95
96
  }
96
97
  export declare const ACCEPTED_FILE = "context/.delta/.accepted.json";
@@ -137,7 +137,13 @@ function isDeltaBody(v) {
137
137
  Array.isArray(v['items']) &&
138
138
  v['items'].every(isDeltaItem));
139
139
  }
140
- async function readDeltas(root, diagnostics) {
140
+ function targetsOf(data) {
141
+ const items = isRecord(data) ? data['items'] : undefined;
142
+ if (!Array.isArray(items))
143
+ return [];
144
+ return items.flatMap((i) => isRecord(i) && typeof i['target'] === 'string' ? [i['target']] : []);
145
+ }
146
+ async function readDeltas(root, diagnostics, broken) {
141
147
  let names = [];
142
148
  try {
143
149
  names = (await readdir(path.join(root, DELTA_DIR))).filter((f) => /\.ya?ml$/.test(f)).sort();
@@ -165,6 +171,7 @@ async function readDeltas(root, diagnostics) {
165
171
  if (issues.length > 0) {
166
172
  for (const i of issues)
167
173
  diagnostics.push({ file, message: `${i.path || '/'} ${i.message}`.trim() });
174
+ broken.push({ file, targets: targetsOf(data) });
168
175
  continue;
169
176
  }
170
177
  if (!isDeltaBody(data))
@@ -214,7 +221,8 @@ export async function checkDelta(root) {
214
221
  if (!git(root, ['rev-parse', '--verify', 'HEAD']).ok)
215
222
  throw new UsageError('delta: нужен git-репозиторий с коммитом HEAD');
216
223
  const diagnostics = [];
217
- const deltas = await readDeltas(root, diagnostics);
224
+ const broken = [];
225
+ const deltas = await readDeltas(root, diagnostics, broken);
218
226
  const roots = await docRoots(root);
219
227
  const status = git(root, ['diff', '--name-status', '--no-renames', 'HEAD', '--', ...roots]).out;
220
228
  const untracked = git(root, ['ls-files', '--others', '--exclude-standard', '--', ...roots]).out;
@@ -241,9 +249,15 @@ export async function checkDelta(root) {
241
249
  ...deltas.flatMap((d) => d.items.filter((i) => i.op === 'revise')).map((i) => i.target),
242
250
  ...accepted.map((a) => a.target),
243
251
  ];
252
+ const hidden = new Map();
244
253
  for (const r of revisions) {
245
254
  if (revise.some((t) => matchesTarget(t, r.file)))
246
255
  continue;
256
+ const cause = broken.find((b) => b.targets.some((t) => matchesTarget(t, r.file)));
257
+ if (cause !== undefined) {
258
+ hidden.set(cause.file, (hidden.get(cause.file) ?? 0) + 1);
259
+ continue;
260
+ }
247
261
  diagnostics.push({
248
262
  file: r.file,
249
263
  message: r.deleted
@@ -251,12 +265,39 @@ export async function checkDelta(root) {
251
265
  : `изменено существующее знание без пункта revise в дельте: ${r.lost.join('; ')}`,
252
266
  });
253
267
  }
254
- return { deltas, revisions, changed, diagnostics, accepted };
268
+ for (const [file, n] of hidden)
269
+ diagnostics.push({
270
+ file,
271
+ message: `дельта не прошла схему — нарушения «без revise» по её целям (${n}) скрыты как следствие; исправь схему`,
272
+ });
273
+ const warnings = committedDeltas(root).map((file) => ({
274
+ file,
275
+ message: `дельта попала в коммит — закрой её: ai delta close --name ${deltaName(file)}; пункты, выполненные в коммитах после её появления, засчитываются`,
276
+ }));
277
+ return { deltas, revisions, changed, diagnostics, warnings, accepted };
278
+ }
279
+ const deltaName = (file) => path.posix.basename(file).replace(/\.ya?ml$/, '');
280
+ function committedDeltas(root) {
281
+ return git(root, ['ls-files', '--', DELTA_DIR])
282
+ .out.split('\n')
283
+ .filter((f) => /\.ya?ml$/.test(f));
284
+ }
285
+ function committedSince(root, file) {
286
+ const added = git(root, ['log', '--diff-filter=A', '--format=%H', '-1', '--', file]).out.trim();
287
+ if (added === '')
288
+ return [];
289
+ const range = git(root, ['rev-parse', '--verify', '-q', `${added}^`]).ok
290
+ ? `${added}^..HEAD`
291
+ : 'HEAD';
292
+ return git(root, ['log', '--format=', '--name-only', '--no-renames', range])
293
+ .out.split('\n')
294
+ .filter((f) => f !== '' && isDoc(f));
255
295
  }
256
296
  export function unfinishedItems(root, result, deltas = result.deltas) {
257
297
  const out = [];
258
- const touched = [...result.changed, ...result.revisions.map((r) => r.file)];
259
298
  for (const d of deltas) {
299
+ const committed = committedSince(root, d.file);
300
+ const touched = [...result.changed, ...result.revisions.map((r) => r.file), ...committed];
260
301
  d.items.forEach((item, i) => {
261
302
  const done = isDeltaTarget(item.target) ||
262
303
  (item.op === 'add'
@@ -1,25 +1,32 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { mkdir, rm, writeFile } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { ACCEPTED_FILE, acceptedOf, checkDelta, matchesTarget, revisionLines, unfinishedItems, } from './delta.js';
4
5
  import { DELTA_DIR } from '#shared/layout';
5
6
  import { UsageError } from '#shared/diagnostics';
6
- import { report } from '#shared/report';
7
+ import { report, warn } from '#shared/report';
7
8
  export async function runDelta(command, args) {
8
9
  if (command !== 'check' && command !== 'close')
9
10
  throw new UsageError(`неизвестная команда: delta ${command ?? ''}`.trimEnd());
10
11
  const result = await checkDelta(args.root);
11
- for (const line of revisionLines(result.deltas))
12
+ const ownFile = args.name === undefined ? undefined : `${DELTA_DIR}/${args.name}.yml`;
13
+ const isForeign = (d) => ownFile !== undefined && d.file.startsWith(`${DELTA_DIR}/`) && d.file !== ownFile;
14
+ const foreign = result.diagnostics.filter(isForeign);
15
+ const errors = result.diagnostics.filter((d) => !isForeign(d));
16
+ warn(result.warnings);
17
+ if (foreign.length > 0) {
18
+ warn(foreign);
19
+ const files = new Set(foreign.map((d) => d.file)).size;
20
+ console.error(`дельты прошлых сессий с нарушениями (${files}): исправь их или удали, если их сессии не продолжатся — сессию ${args.name ?? ''} они не валят`);
21
+ }
22
+ const own = ownFile === undefined ? result.deltas : result.deltas.filter((d) => d.file === ownFile);
23
+ for (const line of revisionLines(own))
12
24
  console.log(line);
13
25
  if (command === 'check')
14
- return report('delta check', result.diagnostics);
15
- const own = args.name === undefined
16
- ? result.deltas
17
- : result.deltas.filter((d) => d.file === `${DELTA_DIR}/${args.name ?? ''}.yml`);
18
- if (args.name !== undefined && own.length === 0)
19
- return report('delta close', [
20
- { file: `${DELTA_DIR}/${args.name}.yml`, message: 'дельты нет — закрывать нечего' },
21
- ]);
22
- const diagnostics = [...result.diagnostics, ...unfinishedItems(args.root, result, own)];
26
+ return report('delta check', errors);
27
+ if (ownFile !== undefined && own.length === 0 && !existsSync(path.join(args.root, ownFile)))
28
+ return report('delta close', [{ file: ownFile, message: 'дельты нет — закрывать нечего' }]);
29
+ const diagnostics = [...errors, ...unfinishedItems(args.root, result, own)];
23
30
  if (diagnostics.length > 0)
24
31
  return report('delta close', diagnostics);
25
32
  const still = result.revisions.map((r) => r.file);
package/dist/registry.js CHANGED
@@ -43,6 +43,7 @@ export const COMMAND_NODES = [
43
43
  path: ['delta', 'check'],
44
44
  kind: 'command',
45
45
  effect: 'query',
46
+ options: '[--name <дельта>]',
46
47
  run: (args) => runDelta('check', args),
47
48
  },
48
49
  {
@@ -1,2 +1,3 @@
1
1
  import { type Diagnostic } from './diagnostics.ts';
2
2
  export declare function report(label: string, diags: Diagnostic[]): number;
3
+ export declare function warn(diags: Diagnostic[]): void;
@@ -9,3 +9,7 @@ export function report(label, diags) {
9
9
  console.error(`${label}: нарушений ${diags.length}`);
10
10
  return 1;
11
11
  }
12
+ export function warn(diags) {
13
+ for (const d of diags)
14
+ console.error(`предупреждение: ${formatDiagnostic(d)}`);
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shahimat/ai-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Команды для проектов на контрактах ai-prompts: генераторы, проверки, механизм ожиданий и роли",
5
5
  "license": "MIT",
6
6
  "author": "Gevorg Nakhapetyan",
@@ -65,14 +65,14 @@
65
65
  "@types/node": "^24.19.2",
66
66
  "typescript": "^7.0.2",
67
67
  "vitest": "^5.0.4",
68
- "@shahimat/cli": "0.1.0"
68
+ "@shahimat/cli": "0.1.1"
69
69
  },
70
70
  "publishConfig": {
71
71
  "access": "public",
72
72
  "registry": "https://registry.npmjs.org/"
73
73
  },
74
74
  "peerDependencies": {
75
- "vitest": ">=4"
75
+ "vitest": ">=3"
76
76
  },
77
77
  "peerDependenciesMeta": {
78
78
  "vitest": {