local-kanban 2.0.0-beta.5 → 2.0.0-beta.6
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 +6 -0
- package/package.json +1 -1
- package/src/routes/tasks.js +23 -9
- package/src/shipped.js +6 -0
- package/src/tree-print.js +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,12 @@ version it goes out as and opens an empty one again. Released sections are histo
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [2.0.0-beta.6]
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Words typed to the terminal of a task in Review that only lead to commits of other tasks or a release (such as «roll it out») no longer keep the handed-in task in Doing: when the turn changed nothing but commits and none of them names the task, it goes back to Review. A task without an issue keeps the old rule — a commit naming nothing may be its own.
|
|
16
|
+
|
|
11
17
|
## [2.0.0-beta.5]
|
|
12
18
|
|
|
13
19
|
### Fixed
|
package/package.json
CHANGED
package/src/routes/tasks.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolveProject } from './projects.js';
|
|
|
10
10
|
import { kick } from '../sync/worker.js';
|
|
11
11
|
import { LABELS } from '../sync/github.js';
|
|
12
12
|
import { repoHttpsBase } from '../repo-base.js';
|
|
13
|
-
import { STAT_FORMAT, mirrorName, statCommit, wordsOf } from '../shipped.js';
|
|
13
|
+
import { STAT_FORMAT, mirrorName, namesTask, statCommit, wordsOf } from '../shipped.js';
|
|
14
14
|
import { isManual, listShape, noclaudeFilter, snapshot, taskEvent } from '../task-shape.js';
|
|
15
15
|
import { requestSession, requestHostSession, bindSession, taskSessions, mayReprep, unclaimedLaunch, claimLaunch, markLaunch } from '../claude-sessions.js';
|
|
16
16
|
import { taskFeed } from '../work-feed.js';
|
|
@@ -19,7 +19,7 @@ import { currentWorkProjects, liveTasks, terminalQuestions } from '../claude-liv
|
|
|
19
19
|
import { activeBoardSessions, doneRuns, nowRuns } from '../claude-now.js';
|
|
20
20
|
import { closeFinishedTerminals, taskTerminal } from '../phantom.js';
|
|
21
21
|
import { CHECK_LABEL, OPEN_CHECK, isCheck, leadCheck, openCheck, secondCheck } from '../review-check.js';
|
|
22
|
-
import { treePrint } from '../tree-print.js';
|
|
22
|
+
import { treePrint, turnCommits } from '../tree-print.js';
|
|
23
23
|
import { bin } from '../bin-path.js';
|
|
24
24
|
|
|
25
25
|
const ATTACH_DIR = join(DATA_DIR, 'attachments');
|
|
@@ -65,6 +65,15 @@ async function commitUrlFor(task) {
|
|
|
65
65
|
|
|
66
66
|
// and not the newest commit of the project, whoever made it
|
|
67
67
|
const STAT_SCAN = 300;
|
|
68
|
+
/** The words that name `task` in a commit of the repository at `path` (wordsOf) and the prefixes of the board. */
|
|
69
|
+
async function taskWords(task, path) {
|
|
70
|
+
const issueNo = task.gh_issue_number;
|
|
71
|
+
const mirror = mirrorName(ghOwner(), ghRepo());
|
|
72
|
+
const base = issueNo && mirror ? await repoHttpsBase(path) : null;
|
|
73
|
+
const home = Boolean(base) && base.toLowerCase() === `https://github.com/${mirror}`.toLowerCase();
|
|
74
|
+
const known = new Set(db.prepare('SELECT upper(prefix) AS prefix FROM projects').all().map((p) => p.prefix));
|
|
75
|
+
return { words: wordsOf(task.key, issueNo, mirror, home), known };
|
|
76
|
+
}
|
|
68
77
|
function attachGitStat(task, startedAt) {
|
|
69
78
|
const project = db.prepare('SELECT path FROM projects WHERE id = ?').get(task.project_id);
|
|
70
79
|
if (!project?.path) return;
|
|
@@ -73,14 +82,11 @@ function attachGitStat(task, startedAt) {
|
|
|
73
82
|
const git = (args) => new Promise((done) => execFile(bin('git'), ['-C', project.path, ...args], { timeout: 5000, maxBuffer: 16 * 1024 * 1024 }, (err, out) => done(err ? null : out)));
|
|
74
83
|
(async () => {
|
|
75
84
|
try {
|
|
76
|
-
const
|
|
77
|
-
const base = issueNo && mirror ? await repoHttpsBase(project.path) : null;
|
|
78
|
-
const home = Boolean(base) && base.toLowerCase() === `https://github.com/${mirror}`.toLowerCase();
|
|
85
|
+
const { words, known } = await taskWords(task, project.path);
|
|
79
86
|
const log = await git(['log', '-n', String(STAT_SCAN), `--format=${STAT_FORMAT}`]);
|
|
80
87
|
if (log === null) return; // not a git repository, or no git — nothing to show
|
|
81
|
-
const known = new Set(db.prepare('SELECT upper(prefix) AS prefix FROM projects').all().map((p) => p.prefix));
|
|
82
88
|
const since = startedAt ? Math.floor(new Date(startedAt + 'Z').getTime() / 1000) : null;
|
|
83
|
-
const hash = statCommit(log,
|
|
89
|
+
const hash = statCommit(log, words, { issue: issueNo, since, known });
|
|
84
90
|
if (!hash) return;
|
|
85
91
|
const stdout = await git(['log', '-1', '--format=%h|%ct|%s', '--shortstat', hash]);
|
|
86
92
|
const lines = String(stdout || '').trim().split('\n').filter(Boolean);
|
|
@@ -361,8 +367,16 @@ export async function settleTurns(states) {
|
|
|
361
367
|
const elsewhere = db.prepare('SELECT 1 FROM task_events WHERE session_id = ? AND task_id != ? AND id > ? LIMIT 1').get(m.sid, st.id, m.event);
|
|
362
368
|
if (!elsewhere) {
|
|
363
369
|
const before = await m.print;
|
|
364
|
-
const
|
|
365
|
-
|
|
370
|
+
const path = db.prepare('SELECT p.path FROM tasks t JOIN projects p ON p.id = t.project_id WHERE t.id = ?').get(st.id)?.path;
|
|
371
|
+
const now = await treePrint(path);
|
|
372
|
+
if (before === null || now === null) continue; // cannot tell
|
|
373
|
+
if (before !== now) {
|
|
374
|
+
const log = await turnCommits(path, before, now, STAT_FORMAT);
|
|
375
|
+
const t = log === null ? null : resolveTask(String(st.id));
|
|
376
|
+
if (!t) continue; // changed files — or cannot tell
|
|
377
|
+
const { words, known } = await taskWords(t, path);
|
|
378
|
+
if (namesTask(log, words, { issue: t.gh_issue_number, known })) continue; // committed for it
|
|
379
|
+
}
|
|
366
380
|
}
|
|
367
381
|
back.push({ id: st.id, ...m });
|
|
368
382
|
}
|
package/src/shipped.js
CHANGED
|
@@ -68,6 +68,12 @@ export function statCommit(out, words, { issue = null, since = null, known = nul
|
|
|
68
68
|
return head.hash;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export function namesTask(out, words, { issue = null, known = null } = {}) {
|
|
72
|
+
const commits = parseLog(out, known);
|
|
73
|
+
if (!commits.length) return true;
|
|
74
|
+
return commits.some((c) => words.some((w) => c.words.has(w)) || (!issue && !ownKeys(c.subject, known).length));
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
export function branchesOf(key, names) {
|
|
72
78
|
const k = new RegExp(`(^|[-/_])${key}($|[-/_])`, 'i');
|
|
73
79
|
return names.filter((n) => k.test(n.replace(/^(origin\/)?claude\//, '')));
|
package/src/tree-print.js
CHANGED
|
@@ -29,6 +29,16 @@ export async function treePrint(path) {
|
|
|
29
29
|
if (untracked.length > UNTRACKED_MAX || !top) return null; // too many to look at, or no root to find them: cannot tell
|
|
30
30
|
const root = top.toString('utf8').trim();
|
|
31
31
|
const stamps = await Promise.all(untracked.map((f) => stat(join(root, f)).then((s) => `${f}\0${s.size}\0${s.mtimeMs}`, () => `${f}\0-`)));
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const rest = createHash('sha1').update(status).update('\0').update(diff || '').update('\0').update(stamps.join('\0')).digest('hex');
|
|
33
|
+
return `${head ? head.toString('utf8').trim() : ''}:${rest}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function turnCommits(path, before, now, format) {
|
|
37
|
+
const cut = (p) => { const i = String(p || '').indexOf(':'); return i > 0 ? [p.slice(0, i), p.slice(i + 1)] : [null, null]; };
|
|
38
|
+
const [a, restA] = cut(before);
|
|
39
|
+
const [b, restB] = cut(now);
|
|
40
|
+
if (!a || !b || a === b || restA !== restB) return null;
|
|
41
|
+
if (await git(path, ['merge-base', '--is-ancestor', a, b]) === null) return null; // a branch switched, a reset: cannot tell
|
|
42
|
+
const out = await git(path, ['log', `--format=${format}`, `${a}..${b}`]);
|
|
43
|
+
return out === null ? null : out.toString('utf8');
|
|
34
44
|
}
|