zames_pro 2.63.1 → 2.64.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 +31 -1
- package/README.md +4 -0
- package/dist/attach-refs.js +90 -0
- package/dist/changelog.js +80 -0
- package/dist/index.js +2 -84
- package/package.json +2 -1
- package/scripts/changelog-draft.mts +56 -0
- package/scripts/coverage-gate.mjs +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.64.1] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- README: добавлен скриншот терминальной сессии (`docs/demo.png`) под
|
|
15
|
+
шапкой, чтобы интерфейс был виден сразу; скрипт воспроизводимого
|
|
16
|
+
VHS-демо (`docs/demo.tape`) для будущего GIF.
|
|
17
|
+
|
|
18
|
+
## [2.64.0] - 2026-10-06
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Генератор черновика CHANGELOG из conventional-commits (BACKLOG D2):
|
|
23
|
+
`npm run changelog:draft [ref]` печатает готовый блок для «Unreleased» из
|
|
24
|
+
коммитов после последнего тега (или указанного ref). Чистый модуль
|
|
25
|
+
`src/changelog.ts` (`parseConventionalCommit` / `groupCommits` /
|
|
26
|
+
`renderChangelogDraft`, покрыт тестами). Файл `CHANGELOG.md` НЕ
|
|
27
|
+
перезаписывается автоматически — черновик ревьюится вручную.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Рефакторинг (BACKLOG C3, шаг 1): разрешение путей вложения и инлайн
|
|
32
|
+
`@file`-ссылок вынесены из `src/index.ts` в `src/attach-refs.ts`
|
|
33
|
+
(`resolveAttachPath` / `inlineAtRefs`) — теперь это отдельный тестируемый
|
|
34
|
+
модуль.
|
|
35
|
+
|
|
8
36
|
## [2.63.1] - 2026-10-06
|
|
9
37
|
|
|
10
38
|
### Fixed
|
|
@@ -328,7 +356,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
328
356
|
- Session banner warns when no saved DeepSeek session exists.
|
|
329
357
|
- `--no-color` flag (explicit `NO_COLOR`).
|
|
330
358
|
|
|
331
|
-
[Unreleased]: https://github.com/Viqto0r/zames_pro/compare/v2.
|
|
359
|
+
[Unreleased]: https://github.com/Viqto0r/zames_pro/compare/v2.64.1...HEAD
|
|
360
|
+
[2.64.1]: https://github.com/Viqto0r/zames_pro/compare/v2.64.0...v2.64.1
|
|
361
|
+
[2.64.0]: https://github.com/Viqto0r/zames_pro/compare/v2.63.1...v2.64.0
|
|
332
362
|
[2.63.1]: https://github.com/Viqto0r/zames_pro/compare/v2.63.0...v2.63.1
|
|
333
363
|
[2.63.0]: https://github.com/Viqto0r/zames_pro/compare/v2.61.0...v2.63.0
|
|
334
364
|
[2.61.0]: https://github.com/Viqto0r/zames_pro/compare/v2.60.0...v2.61.0
|
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ directory, reads and edits files, runs commands, and commits to git.
|
|
|
23
23
|
> No API key required — it drives the DeepSeek web chat like a regular user
|
|
24
24
|
> through a real (headless) browser.
|
|
25
25
|
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/demo.png" alt="zames session in the terminal" width="860">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
26
30
|
## How it works
|
|
27
31
|
|
|
28
32
|
zames does not call the model API. It launches a headless Chromium with a
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { looksLikeFilePath, isImageName } from './attachments.js';
|
|
4
|
+
import { extractAtFileRefs } from './path-token.js';
|
|
5
|
+
// Path resolution and `@file` reference inlining (BACKLOG B5, C3).
|
|
6
|
+
// Extracted from src/index.ts so the logic is unit-testable without the whole
|
|
7
|
+
// CLI. This module touches only the filesystem (no browser/terminal).
|
|
8
|
+
const NL = String.fromCharCode(10);
|
|
9
|
+
// Resolve a pasted string into a path to an EXISTING file. Handles quoted
|
|
10
|
+
// paths (drag&drop from some file managers adds quotes) and paths relative to
|
|
11
|
+
// the working directory. Returns null when nothing matches.
|
|
12
|
+
export async function resolveAttachPath(workdir, raw) {
|
|
13
|
+
let s = String(raw || '').trim();
|
|
14
|
+
if (!s)
|
|
15
|
+
return null;
|
|
16
|
+
if ((s.startsWith('"') && s.endsWith('"')) ||
|
|
17
|
+
(s.startsWith("'") && s.endsWith("'"))) {
|
|
18
|
+
s = s.slice(1, -1);
|
|
19
|
+
}
|
|
20
|
+
s = s.replace(/\\ /g, ' ');
|
|
21
|
+
if (!looksLikeFilePath(s) && !isImageName(s))
|
|
22
|
+
return null;
|
|
23
|
+
const candidates = path.isAbsolute(s)
|
|
24
|
+
? [s]
|
|
25
|
+
: [
|
|
26
|
+
// Most common: relative to the project (working) directory.
|
|
27
|
+
path.resolve(workdir, s),
|
|
28
|
+
// The operator may paste a path relative to the terminal cwd (which
|
|
29
|
+
// can be the parent of the project) or copy it with the project name
|
|
30
|
+
// included, e.g. "zames_pro/tmp/image.png" while workdir is already
|
|
31
|
+
// .../zames_pro. Try both so the marker appears instead of raw text.
|
|
32
|
+
path.resolve(process.cwd(), s),
|
|
33
|
+
path.resolve(workdir, '..', s),
|
|
34
|
+
];
|
|
35
|
+
for (const c of candidates) {
|
|
36
|
+
const st = await fs.stat(c).catch(() => null);
|
|
37
|
+
if (st && st.isFile())
|
|
38
|
+
return c;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Per-file and total caps for `@path` inlining (B5). A reference should SAVE
|
|
43
|
+
// round-trips, not flood the context: a huge file is truncated with a note
|
|
44
|
+
// telling the agent to read the rest itself.
|
|
45
|
+
export const AT_REF_FILE_CAP = 60 * 1024;
|
|
46
|
+
export const AT_REF_TOTAL_CAP = 200 * 1024;
|
|
47
|
+
// Inline `@path` references found in a task text (B5). Returns the augmented
|
|
48
|
+
// text plus the list of files actually inlined (for the operator notice).
|
|
49
|
+
// Unknown/unreadable references are left in place untouched -- they may be
|
|
50
|
+
// part of the prose; we never fail the task over a missing file.
|
|
51
|
+
export async function inlineAtRefs(workdir, text) {
|
|
52
|
+
const refs = extractAtFileRefs(text);
|
|
53
|
+
if (!refs.length)
|
|
54
|
+
return { text, inlined: [] };
|
|
55
|
+
const blocks = [];
|
|
56
|
+
const inlined = [];
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (const ref of refs) {
|
|
59
|
+
if (total >= AT_REF_TOTAL_CAP)
|
|
60
|
+
break;
|
|
61
|
+
const file = await resolveAttachPath(workdir, ref);
|
|
62
|
+
if (!file)
|
|
63
|
+
continue;
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = await fs.readFile(file, 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
let body = raw;
|
|
72
|
+
let note = '';
|
|
73
|
+
if (body.length > AT_REF_FILE_CAP) {
|
|
74
|
+
body = body.slice(0, AT_REF_FILE_CAP);
|
|
75
|
+
note = NL + NL + '(truncated at ' + AT_REF_FILE_CAP + ' chars)';
|
|
76
|
+
}
|
|
77
|
+
total += body.length;
|
|
78
|
+
inlined.push(path.relative(workdir, file) || file);
|
|
79
|
+
blocks.push('### ' + ref + NL + '```' + NL + body + NL + '```' + note);
|
|
80
|
+
}
|
|
81
|
+
if (!blocks.length)
|
|
82
|
+
return { text, inlined: [] };
|
|
83
|
+
const section = NL +
|
|
84
|
+
NL +
|
|
85
|
+
'## Files referenced with @ in the task' +
|
|
86
|
+
NL +
|
|
87
|
+
NL +
|
|
88
|
+
blocks.join(NL + NL);
|
|
89
|
+
return { text: text + section, inlined };
|
|
90
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Conventional-Commits -> CHANGELOG draft (BACKLOG D2). PURE and
|
|
2
|
+
// unit-tested: the draft is built from git log OUTSIDE the release flow
|
|
3
|
+
// (scripts/changelog-draft.mts), so a misparse never touches CHANGELOG.md.
|
|
4
|
+
// We do NOT auto-write the file: a human (or the agent) reviews the draft
|
|
5
|
+
// first -- the changelog must stay a deliberate document, not a byproduct.
|
|
6
|
+
// Parse one commit SUBJECT into a conventional commit, or null when it
|
|
7
|
+
// does not follow the format (a free-form subject is skipped from the draft).
|
|
8
|
+
// Supported forms:
|
|
9
|
+
// feat: add X feat(cli): add X
|
|
10
|
+
// fix!: change Y feat!: breaking
|
|
11
|
+
// Explicit breaking markers: `!` after the type/scope. A BREAKING CHANGE
|
|
12
|
+
// body footer is beyond a subject-only parser (git log %s only) -- out of scope.
|
|
13
|
+
export function parseConventionalCommit(subject) {
|
|
14
|
+
const s = String(subject ?? '').trim();
|
|
15
|
+
if (!s)
|
|
16
|
+
return null;
|
|
17
|
+
// <type>(<scope>)!?: <description>
|
|
18
|
+
const m = /^([a-zA-Z]+)(\(.+?\))?(!)?:\s*(.+)$/.exec(s);
|
|
19
|
+
if (!m)
|
|
20
|
+
return null;
|
|
21
|
+
const type = m[1].toLowerCase();
|
|
22
|
+
const scope = m[2] ? m[2].slice(1, -1).trim() : undefined;
|
|
23
|
+
const breaking = m[3] === '!';
|
|
24
|
+
const description = (m[4] || '').trim();
|
|
25
|
+
if (!description)
|
|
26
|
+
return null;
|
|
27
|
+
return { type, scope, breaking, description };
|
|
28
|
+
}
|
|
29
|
+
const KIND_BY_TYPE = {
|
|
30
|
+
feat: 'Added',
|
|
31
|
+
fix: 'Fixed',
|
|
32
|
+
perf: 'Changed',
|
|
33
|
+
refactor: 'Changed',
|
|
34
|
+
style: 'Changed',
|
|
35
|
+
revert: 'Changed',
|
|
36
|
+
remove: 'Removed',
|
|
37
|
+
};
|
|
38
|
+
// Types that never belong in a user-facing changelog (build/CI/internal).
|
|
39
|
+
const SKIP_TYPES = new Set(['chore', 'ci', 'test', 'build', 'docs', 'internal']);
|
|
40
|
+
// Group parsed commits by changelog kind. Each entry is a short
|
|
41
|
+
// imperative line; a scope is prefixed as `**scope:** ` and a breaking marker
|
|
42
|
+
// is appended as `(BREAKING)`. Order within a group is preserved (commit
|
|
43
|
+
// order), and duplicate lines are dropped so a rebase that repeats a subject
|
|
44
|
+
// does not flip the draft twice.
|
|
45
|
+
export function groupCommits(forCommit) {
|
|
46
|
+
const groups = {
|
|
47
|
+
Added: [],
|
|
48
|
+
Fixed: [],
|
|
49
|
+
Changed: [],
|
|
50
|
+
Removed: [],
|
|
51
|
+
};
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
for (const c of forCommit || []) {
|
|
54
|
+
const kind = KIND_BY_TYPE[c.type];
|
|
55
|
+
if (!kind || SKIP_TYPES.has(c.type))
|
|
56
|
+
continue;
|
|
57
|
+
const prefix = c.scope ? '**' + c.scope + ':** ' : '';
|
|
58
|
+
const line = '- ' + prefix + c.description + (c.breaking ? ' (BREAKING)' : '');
|
|
59
|
+
if (seen.has(line))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(line);
|
|
62
|
+
groups[kind].push(line);
|
|
63
|
+
}
|
|
64
|
+
return groups;
|
|
65
|
+
}
|
|
66
|
+
// Render the groups as a changelog body (the parts under a version
|
|
67
|
+
// heading), with the standard Keep-a-Changelog order and empty groups
|
|
68
|
+
// omitted. Returns '' when there is nothing to report.
|
|
69
|
+
export function renderChangelogDraft(groups) {
|
|
70
|
+
const order = ['Added', 'Fixed', 'Changed', 'Removed'];
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const kind of order) {
|
|
73
|
+
const items = groups[kind] || [];
|
|
74
|
+
if (!items.length)
|
|
75
|
+
continue;
|
|
76
|
+
out.push('## ' + kind, '');
|
|
77
|
+
out.push(...items, '');
|
|
78
|
+
}
|
|
79
|
+
return out.join('\n').trimEnd();
|
|
80
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createTodoStore, getTodos, setTodos, resetTodos, renderTodos, } from '.
|
|
|
11
11
|
import { runAgentLoop } from './agent-loop.js';
|
|
12
12
|
import { createSpinner } from './spinner.js';
|
|
13
13
|
import { LineEditor, expandPastes, pasteReplacement, formatCompactTokens, } from './input.js';
|
|
14
|
-
import { parseImagePaste, extForMime, saveToTemp, guessMime,
|
|
14
|
+
import { parseImagePaste, extForMime, saveToTemp, guessMime, formatSize, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, writeClipboardText, contentHash, } from './attachments.js';
|
|
15
15
|
import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, } from './config.js';
|
|
16
16
|
import { runConfigMenu } from './config-menu.js';
|
|
17
17
|
import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n.js';
|
|
@@ -23,7 +23,7 @@ import { formatDiff, formatDiffStat, formatContextSources, diffGitArgs, parseTra
|
|
|
23
23
|
import { performCompact } from './compact.js';
|
|
24
24
|
import { Scheduler, parseInterval, formatInterval, formatJobLine, parseCron, } from './scheduler.js';
|
|
25
25
|
import { renderMarkdown, setAnswerWidth } from './markdown.js';
|
|
26
|
-
import {
|
|
26
|
+
import { resolveAttachPath, inlineAtRefs } from './attach-refs.js';
|
|
27
27
|
import { closeWeb } from './web.js';
|
|
28
28
|
import { assertCommandInsideRoot, runShell } from './shell.js';
|
|
29
29
|
import { saveSession, loadLastSession, readSession, listSessions, sessionsDir, loadHistory, saveHistory, } from './sessions.js';
|
|
@@ -627,88 +627,6 @@ function buildSlashCommands() {
|
|
|
627
627
|
// We put the agent's temporary files (one-off scripts, etc.) in
|
|
628
628
|
// <project>/tmp — this folder is in .gitignore and is cleaned on every launch.
|
|
629
629
|
const TMP_DIR = path.join(__dirname, '..', 'tmp');
|
|
630
|
-
// Resolve a pasted string into a path to an existing file. Handles quoted
|
|
631
|
-
// paths (drag&drop from some file managers adds quotes) and paths relative to
|
|
632
|
-
// the working directory.
|
|
633
|
-
async function resolveAttachPath(workdir, raw) {
|
|
634
|
-
let s = String(raw || '').trim();
|
|
635
|
-
if (!s)
|
|
636
|
-
return null;
|
|
637
|
-
if ((s.startsWith('"') && s.endsWith('"')) ||
|
|
638
|
-
(s.startsWith("'") && s.endsWith("'"))) {
|
|
639
|
-
s = s.slice(1, -1);
|
|
640
|
-
}
|
|
641
|
-
s = s.replace(/\\ /g, ' ');
|
|
642
|
-
if (!looksLikeFilePath(s) && !isImageName(s))
|
|
643
|
-
return null;
|
|
644
|
-
const candidates = path.isAbsolute(s)
|
|
645
|
-
? [s]
|
|
646
|
-
: [
|
|
647
|
-
// Most common: relative to the project (working) directory.
|
|
648
|
-
path.resolve(workdir, s),
|
|
649
|
-
// The operator may paste a path relative to the terminal's cwd (which
|
|
650
|
-
// can be the parent of the project) or copy it with the project name
|
|
651
|
-
// included, e.g. "zames_pro/tmp/image.png" while workdir is already
|
|
652
|
-
// .../zames_pro. Try both so the marker appears instead of raw text.
|
|
653
|
-
path.resolve(process.cwd(), s),
|
|
654
|
-
path.resolve(workdir, '..', s),
|
|
655
|
-
];
|
|
656
|
-
for (const c of candidates) {
|
|
657
|
-
const st = await fs.stat(c).catch(() => null);
|
|
658
|
-
if (st && st.isFile())
|
|
659
|
-
return c;
|
|
660
|
-
}
|
|
661
|
-
return null;
|
|
662
|
-
}
|
|
663
|
-
// Per-file and total caps for `@path` inlining (B5). A reference should SAVE
|
|
664
|
-
// round-trips, not flood the context: a huge file is truncated with a note
|
|
665
|
-
// telling the agent to read the rest itself.
|
|
666
|
-
const AT_REF_FILE_CAP = 60 * 1024;
|
|
667
|
-
const AT_REF_TOTAL_CAP = 200 * 1024;
|
|
668
|
-
// Inline `@path` references found in a task text (B5). Returns the augmented
|
|
669
|
-
// text plus the list of files actually inlined (for the operator notice).
|
|
670
|
-
// Unknown/unreadable references are left in place untouched — they may be part
|
|
671
|
-
// of the prose; we never fail the task over a missing file.
|
|
672
|
-
async function inlineAtRefs(workdir, text) {
|
|
673
|
-
const refs = extractAtFileRefs(text);
|
|
674
|
-
if (!refs.length)
|
|
675
|
-
return { text, inlined: [] };
|
|
676
|
-
const blocks = [];
|
|
677
|
-
const inlined = [];
|
|
678
|
-
let total = 0;
|
|
679
|
-
for (const ref of refs) {
|
|
680
|
-
if (total >= AT_REF_TOTAL_CAP)
|
|
681
|
-
break;
|
|
682
|
-
const file = await resolveAttachPath(workdir, ref);
|
|
683
|
-
if (!file)
|
|
684
|
-
continue;
|
|
685
|
-
let raw;
|
|
686
|
-
try {
|
|
687
|
-
raw = await fs.readFile(file, 'utf-8');
|
|
688
|
-
}
|
|
689
|
-
catch {
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
let body = raw;
|
|
693
|
-
let note = '';
|
|
694
|
-
if (body.length > AT_REF_FILE_CAP) {
|
|
695
|
-
body = body.slice(0, AT_REF_FILE_CAP);
|
|
696
|
-
note = NL + NL + '(truncated at ' + AT_REF_FILE_CAP + ' chars)';
|
|
697
|
-
}
|
|
698
|
-
total += body.length;
|
|
699
|
-
inlined.push(path.relative(workdir, file) || file);
|
|
700
|
-
blocks.push('### ' + ref + NL + '```' + NL + body + NL + '```' + note);
|
|
701
|
-
}
|
|
702
|
-
if (!blocks.length)
|
|
703
|
-
return { text, inlined: [] };
|
|
704
|
-
const section = NL +
|
|
705
|
-
NL +
|
|
706
|
-
'## Files referenced with @ in the task' +
|
|
707
|
-
NL +
|
|
708
|
-
NL +
|
|
709
|
-
blocks.join(NL + NL);
|
|
710
|
-
return { text: text + section, inlined };
|
|
711
|
-
}
|
|
712
630
|
async function cleanTmpDir() {
|
|
713
631
|
try {
|
|
714
632
|
await fs.rm(TMP_DIR, { recursive: true, force: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.64.1",
|
|
4
4
|
"description": "Terminal coding agent that drives chat.deepseek.com through Playwright: reads and edits files, runs commands, commits to git.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"clean": "node scripts/clean-dist.mjs",
|
|
22
22
|
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
23
23
|
"coverage:gate": "node scripts/coverage-gate.mjs",
|
|
24
|
+
"changelog:draft": "tsx scripts/changelog-draft.mts",
|
|
24
25
|
"typecheck": "tsc --noEmit",
|
|
25
26
|
"lint": "node scripts/eslint-ts6.mjs",
|
|
26
27
|
"lint:fix": "node scripts/eslint-ts6.mjs --fix",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
parseConventionalCommit,
|
|
4
|
+
groupCommits,
|
|
5
|
+
renderChangelogDraft,
|
|
6
|
+
} from '../src/changelog.js'
|
|
7
|
+
|
|
8
|
+
// Draft a CHANGELOG section from conventional commits since a ref (BACKLOG
|
|
9
|
+
// D2). PRINTS to stdout and never touches CHANGELOG.md -- the operator (or
|
|
10
|
+
// the agent) pastes the result in by hand, so a bad commit subject can never
|
|
11
|
+
// corrupt the changelog.
|
|
12
|
+
//
|
|
13
|
+
// Usage:
|
|
14
|
+
// npm run changelog:draft # since the latest tag, or an explicit ref
|
|
15
|
+
// npm run changelog:draft -- v2.63.0
|
|
16
|
+
|
|
17
|
+
function git(args: string[]): string {
|
|
18
|
+
const r = spawnSync('git', args, { encoding: 'utf-8' })
|
|
19
|
+
if (r.status !== 0) return ''
|
|
20
|
+
return (r.stdout || '').toString()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const argRef = process.argv.slice(2).find((a) => !a.startsWith('-'))
|
|
24
|
+
|
|
25
|
+
// Resolve the start ref: an explicit arg, otherwise the latest tag,
|
|
26
|
+
// otherwise the root commit.
|
|
27
|
+
const latestTag = git(['describe', '--tags', '--abbrev=0']).trim()
|
|
28
|
+
const ref =
|
|
29
|
+
argRef ||
|
|
30
|
+
latestTag ||
|
|
31
|
+
git(['revlist', 'HEAD']).trim().split('\n').pop() ||
|
|
32
|
+
'HEAD'
|
|
33
|
+
|
|
34
|
+
const subjects = git([
|
|
35
|
+
'log',
|
|
36
|
+
'--no-merges',
|
|
37
|
+
'--pretty=format:%s',
|
|
38
|
+
`${ref}..HEAD`,
|
|
39
|
+
])
|
|
40
|
+
.split('\n')
|
|
41
|
+
.map((s) => s.trim())
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
|
|
44
|
+
const parsed = subjects
|
|
45
|
+
.map(parseConventionalCommit)
|
|
46
|
+
.filter((c): c is NonNullable<typeof c> => c !== null)
|
|
47
|
+
const body = renderChangelogDraft(groupCommits(parsed))
|
|
48
|
+
|
|
49
|
+
if (!body) {
|
|
50
|
+
console.error('no conventional commits since ' + ref)
|
|
51
|
+
process.exit(0)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log('## [Unreleased]')
|
|
55
|
+
console.log('')
|
|
56
|
+
console.log(body)
|
|
@@ -30,12 +30,18 @@ const FLOORS = {
|
|
|
30
30
|
'undo.ts': 85,
|
|
31
31
|
'checkpoint.ts': 80,
|
|
32
32
|
'permissions.ts': 85,
|
|
33
|
+
'changelog.ts': 85,
|
|
34
|
+
'attach-refs.ts': 85,
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const run = spawnSync(
|
|
36
38
|
'npx',
|
|
37
39
|
['tsx', '--test', '--experimental-test-coverage', 'test/*.test.ts'],
|
|
38
|
-
{
|
|
40
|
+
{
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
shell: process.platform === 'win32',
|
|
43
|
+
maxBuffer: 1024 * 1024 * 64,
|
|
44
|
+
},
|
|
39
45
|
)
|
|
40
46
|
|
|
41
47
|
const out = (run.stdout || '') + (run.stderr || '')
|