@goodandready/dsh-kanban 0.2.17 → 0.2.19

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/lib/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  runBatch, queueTask, unqueueTask, liveSessions, dispatchNextQueuedTask,
56
56
  PERMISSION_RANKS,
57
57
  } from './launcher.js'
58
- import { cleanupTaskWorktree, checkWorktreeDirty, getTaskDiff, getTaskCommits, mergeTaskWorktree } from './worktree.js'
58
+ import { cleanupTaskWorktree, checkWorktreeDirty, getTaskDiff, getTaskCommits, mergeTaskWorktree, defaultGitRunner } from './worktree.js'
59
59
  import { dispatchMove, MOVE_DETAIL, stopWork, STOP_DETAIL } from './commands.js'
60
60
  import { createGiteaClient } from './gitea.js'
61
61
  import { createGitHubClient } from './github.js'
@@ -75,7 +75,7 @@ import {
75
75
  boardDecomposeDefinition,
76
76
  boardChecklistItemDefinition,
77
77
  } from './board-tool.js'
78
- import { verifySignature, parseEvent } from './webhook.js'
78
+ import { verifySignature, parseEvent, detectWebhookProvider } from './webhook.js'
79
79
  import { planOutbound, planAssign, createOutbox } from './outbound.js'
80
80
  import { computeNextRun } from './cron.js'
81
81
  import { PowerInhibitor } from './power.js'
@@ -659,7 +659,10 @@ export function apply(ctx, config) {
659
659
  ctx.effect(() => ctx.webServer.register({
660
660
  kind: 'exact',
661
661
  path: '/dsh-kanban/sessions',
662
- handler: (req, res) => { reply(res, { sessions: liveSessions({ store, agents: ctx.agents }) }) },
662
+ handler: (req, res) => {
663
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
664
+ reply(res, { sessions: liveSessions({ store, agents: ctx.agents }) })
665
+ },
663
666
  }), 'dsh-kanban: live sessions')
664
667
 
665
668
  ctx.effect(() => ctx.webServer.register({
@@ -697,7 +700,10 @@ export function apply(ctx, config) {
697
700
  ctx.effect(() => ctx.webServer.register({
698
701
  kind: 'exact',
699
702
  path: '/dsh-kanban/archive',
700
- handler: (req, res) => { reply(res, listArchive({ store })) },
703
+ handler: (req, res) => {
704
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
705
+ reply(res, listArchive({ store }))
706
+ },
701
707
  }), 'dsh-kanban: archive')
702
708
 
703
709
  ctx.effect(() => ctx.webServer.register({
@@ -721,7 +727,7 @@ export function apply(ctx, config) {
721
727
  if (req.method !== 'POST') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
722
728
  try {
723
729
  const raw = await readRaw(req)
724
- const isGithub = Boolean(req.headers['x-github-event'] || req.headers['x-hub-signature-256'])
730
+ const isGithub = detectWebhookProvider(req.headers) === 'github'
725
731
  const secretRef = isGithub
726
732
  ? (getConfig().githubWebhookSecretRef || getConfig().webhookSecretRef)
727
733
  : getConfig().webhookSecretRef
@@ -804,6 +810,7 @@ export function apply(ctx, config) {
804
810
  kind: 'exact',
805
811
  path: '/dsh-kanban/board',
806
812
  handler: (req, res) => {
813
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
807
814
  const url = urlOf(req)
808
815
  reply(res, buildBoard({
809
816
  store,
@@ -849,6 +856,7 @@ export function apply(ctx, config) {
849
856
  kind: 'exact',
850
857
  path: '/dsh-kanban/metrics',
851
858
  handler: (req, res) => {
859
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
852
860
  const url = urlOf(req)
853
861
  const days = Number(url.searchParams.get('staleDays'))
854
862
  reply(res, boardMetrics({
@@ -864,13 +872,17 @@ export function apply(ctx, config) {
864
872
  path: '/dsh-kanban/whoami',
865
873
  // Пустой логин — законный ответ: Gitea может быть не настроен или не
866
874
  // отвечать. Браузер по нему просто не рисует «Мои», а не показывает ноль.
867
- handler: async (req, res) => { reply(res, { login: await whoAmI() }) },
875
+ handler: async (req, res) => {
876
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
877
+ reply(res, { login: await whoAmI() })
878
+ },
868
879
  }), 'dsh-kanban: whoami')
869
880
 
870
881
  ctx.effect(() => ctx.webServer.register({
871
882
  kind: 'exact',
872
883
  path: '/dsh-kanban/export.csv',
873
884
  handler: async (req, res) => {
885
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
874
886
  const u = urlOf(req)
875
887
  const board = u.searchParams.get('board') || 'main'
876
888
  const csv = exportTasksCsv({ store, board })
@@ -902,6 +914,7 @@ export function apply(ctx, config) {
902
914
  kind: 'exact',
903
915
  path: '/dsh-kanban/templates',
904
916
  handler: (req, res) => {
917
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
905
918
  const u = new URL(req.url, 'http://localhost')
906
919
  const tid = u.searchParams.get('id')
907
920
  if (tid) {
@@ -1024,7 +1037,7 @@ export function apply(ctx, config) {
1024
1037
  provider: picked.provider,
1025
1038
  model: picked.model,
1026
1039
  mintSessionId: () => SessionId(`kanban-${id}-${randomUUID()}`),
1027
- gitRunner,
1040
+ gitRunner: defaultGitRunner,
1028
1041
  workspaceRegistry: ctx.get('workspaceRegistry'),
1029
1042
  claimAs: await whoAmI(),
1030
1043
  permission: typeof input.permission === 'string' ? input.permission : undefined,
@@ -1317,6 +1330,7 @@ export function apply(ctx, config) {
1317
1330
  kind: 'exact',
1318
1331
  path: '/dsh-kanban/gitea/repos',
1319
1332
  handler: async (req, res) => {
1333
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
1320
1334
  const url = urlOf(req)
1321
1335
  reply(res, await searchRepos({ gitea: git, query: url.searchParams.get('q') || '' }))
1322
1336
  },
@@ -1326,6 +1340,7 @@ export function apply(ctx, config) {
1326
1340
  kind: 'exact',
1327
1341
  path: '/dsh-kanban/gitea/issues',
1328
1342
  handler: async (req, res) => {
1343
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
1329
1344
  const url = urlOf(req)
1330
1345
  reply(res, await listImportable({
1331
1346
  gitea: git,
@@ -1363,6 +1378,7 @@ export function apply(ctx, config) {
1363
1378
  kind: 'exact',
1364
1379
  path: '/dsh-kanban/models',
1365
1380
  handler: async (req, res) => {
1381
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
1366
1382
  const current = ctx.get('agentDefaultModel')?.currentSelection?.() ?? null
1367
1383
  const llmService = ctx.get('llm')
1368
1384
  let providers = []
@@ -1391,6 +1407,7 @@ export function apply(ctx, config) {
1391
1407
  kind: 'prefix',
1392
1408
  path: '/dsh-kanban/session',
1393
1409
  handler: (req, res) => {
1410
+ if (req.method !== 'GET') { writeJson(res, 405, { error: 'method-not-allowed' }); return }
1394
1411
  const m = /^\/dsh-kanban\/session\/([^/]+)\/task\/?$/.exec(urlOf(req).pathname)
1395
1412
  if (m === null) { writeJson(res, 404, { error: 'not-found' }); return }
1396
1413
  reply(res, taskBySession({ store, sessionId: decodeURIComponent(m[1]), agents: ctx.agents }))
package/lib/intake.js CHANGED
@@ -49,18 +49,18 @@ export function watchedRepos({ config, owner }) {
49
49
  */
50
50
  export function shouldTake({ store, owner, repo, issue }) {
51
51
  const number = issue?.number
52
- if (typeof number !== 'number') return { take: false, why: 'без номера' }
52
+ if (typeof number !== 'number') return { take: false, why: 'missing-number' }
53
53
  // Pull request в Gitea — тоже issue, но работой на доске он не является:
54
54
  // он ход работы, а не сама работа.
55
55
  if (issue?.pull_request !== undefined && issue?.pull_request !== null) {
56
- return { take: false, why: 'это pull request' }
56
+ return { take: false, why: 'pull-request-ignored' }
57
57
  }
58
- if (issue?.state === 'closed') return { take: false, why: 'issue закрыт' }
58
+ if (issue?.state === 'closed') return { take: false, why: 'issue-closed' }
59
59
  if (store.findTaskByIssue({ owner, repo, issueNumber: number }) !== undefined) {
60
- return { take: false, why: 'карточка уже есть' }
60
+ return { take: false, why: 'already-exists' }
61
61
  }
62
62
  if (store.isDismissed({ owner, repo, issueNumber: number })) {
63
- return { take: false, why: 'карточку удалили — значит видеть её здесь не хотят' }
63
+ return { take: false, why: 'tombstone-ignored' }
64
64
  }
65
65
  return { take: true, why: '' }
66
66
  }
package/lib/store.js CHANGED
@@ -208,7 +208,6 @@ export function openStore({ dir }) {
208
208
  db.exec('PRAGMA journal_mode = WAL')
209
209
  db.exec('PRAGMA foreign_keys = ON')
210
210
  db.exec('PRAGMA busy_timeout = 5000')
211
- db.exec('PRAGMA busy_timeout = 5000')
212
211
  db.exec(SCHEMA)
213
212
  // База могла быть создана прежней версией. Добавляем недостающие столбцы
214
213
  // поштучно: ALTER на существующий столбец бросает, и это штатный ответ
package/lib/templates.js CHANGED
@@ -1,60 +1,60 @@
1
- // Шаблоны задач для быстрой постановки типовых работ (#220).
1
+ // Task templates for rapid standard work decomposition (#220, #304).
2
2
 
3
3
  export const TASK_TEMPLATES = [
4
4
  {
5
5
  id: 'feature',
6
- title: 'Новая функциональность (Feature)',
7
- promptTemplate: 'Реализовать новую функциональность:\n- Цель:\n- Требования:\n- Границы изменений:',
6
+ title: 'New Feature',
7
+ promptTemplate: 'Implement new feature:\n- Goal:\n- Requirements:\n- Scope of changes:',
8
8
  checklist: [
9
- { id: '1', text: 'Архитектурный план и дизайн-контракт согласованы', required: true, completed: false },
10
- { id: '2', text: 'Код написан с соблюдением стандартов проекта', required: true, completed: false },
11
- { id: '3', text: 'Написаны автоматические модульные/интеграционные тесты', required: true, completed: false },
12
- { id: '4', text: 'Документация обновлена (README, docs/)', required: true, completed: false },
9
+ { id: '1', text: 'Architecture plan and design contract agreed', required: true, completed: false },
10
+ { id: '2', text: 'Code adheres to project guidelines and standards', required: true, completed: false },
11
+ { id: '3', text: 'Unit and integration tests added and passing', required: true, completed: false },
12
+ { id: '4', text: 'Documentation updated (README, docs/)', required: true, completed: false },
13
13
  ],
14
14
  priority: 'medium',
15
15
  },
16
16
  {
17
17
  id: 'bugfix',
18
- title: 'Исправление ошибки (Bugfix)',
19
- promptTemplate: 'Локализовать и исправить дефект:\n- Шаги воспроизведения:\n- Ожидаемое поведение:\n- Фактическое поведение:',
18
+ title: 'Bug Fix',
19
+ promptTemplate: 'Localize and resolve defect:\n- Reproduction steps:\n- Expected behavior:\n- Actual behavior:',
20
20
  checklist: [
21
- { id: '1', text: 'Дефект воспроизведен изолированным тестом', required: true, completed: false },
22
- { id: '2', text: 'Причина устранена без побочных эффектов', required: true, completed: false },
23
- { id: '3', text: 'Регрессионный тест добавлен в test suite', required: true, completed: false },
21
+ { id: '1', text: 'Defect reproduced with isolated test case', required: true, completed: false },
22
+ { id: '2', text: 'Root cause fixed without regressions', required: true, completed: false },
23
+ { id: '3', text: 'Regression test added to test suite', required: true, completed: false },
24
24
  ],
25
25
  priority: 'high',
26
26
  },
27
27
  {
28
28
  id: 'refactor',
29
- title: 'Рефакторинг и оптимизация (Refactoring)',
30
- promptTemplate: 'Провести рефакторинг модуля без изменения внешнего поведения:\n- Целевой модуль:\n- Проблема архитектуры:\n- Критерии завершения:',
29
+ title: 'Refactoring & Optimization',
30
+ promptTemplate: 'Refactor module preserving external behavior:\n- Target module:\n- Architectural issue:\n- Completion criteria:',
31
31
  checklist: [
32
- { id: '1', text: 'Покрыто существующими тестами (100% green)', required: true, completed: false },
33
- { id: '2', text: 'Упрощена структура/устранен техдолг', required: true, completed: false },
34
- { id: '3', text: 'Производительность не деградировала', required: true, completed: false },
32
+ { id: '1', text: 'Covered by existing tests (100% green)', required: true, completed: false },
33
+ { id: '2', text: 'Structure simplified and technical debt eliminated', required: true, completed: false },
34
+ { id: '3', text: 'Performance verified (no degradation)', required: true, completed: false },
35
35
  ],
36
36
  priority: 'low',
37
37
  },
38
38
  {
39
39
  id: 'security',
40
- title: 'Аудит безопасности и зависимостей (Security)',
41
- promptTemplate: 'Проверить безопасность и уязвимости зависимостей:\n- Область проверки:\n- Ревизия зависимостей:',
40
+ title: 'Security & Dependency Audit',
41
+ promptTemplate: 'Verify security and dependency vulnerabilities:\n- Audit scope:\n- Dependency revision:',
42
42
  checklist: [
43
- { id: '1', text: 'Проверен supply-chain и лицензии зависимостей', required: true, completed: false },
44
- { id: '2', text: 'Устранены известные CVE', required: true, completed: false },
45
- { id: '3', text: 'Секреты и токены проверены на отсутствие утечек', required: true, completed: false },
43
+ { id: '1', text: 'Supply-chain and dependency licenses verified', required: true, completed: false },
44
+ { id: '2', text: 'Known CVEs mitigated or resolved', required: true, completed: false },
45
+ { id: '3', text: 'Secrets and credentials verified clean of leaks', required: true, completed: false },
46
46
  ],
47
47
  priority: 'high',
48
48
  },
49
49
  {
50
50
  id: 'release',
51
- title: 'Подготовка и выпуск релиза (Release Prep)',
52
- promptTemplate: 'Подготовить релиз пакета:\n- Версия:\n- Список изменений:\n- Проверки перед публикацией:',
51
+ title: 'Release Preparation',
52
+ promptTemplate: 'Prepare package release:\n- Version:\n- Changelog summary:\n- Pre-release verification:',
53
53
  checklist: [
54
- { id: '1', text: 'Все тесты репозитория пройдены (npm test)', required: true, completed: false },
55
- { id: '2', text: 'Совместимость с ядром подтверждена (npm run compat)', required: true, completed: false },
56
- { id: '3', text: 'Размер упакованного пакета проверен (< 256 КБ)', required: true, completed: false },
57
- { id: '4', text: 'Документация и changelog дополнены', required: true, completed: false },
54
+ { id: '1', text: 'All repository tests pass (npm test)', required: true, completed: false },
55
+ { id: '2', text: 'Core compatibility confirmed (npm run compat)', required: true, completed: false },
56
+ { id: '3', text: 'Package archive size verified (< 256 KiB)', required: true, completed: false },
57
+ { id: '4', text: 'Documentation and changelog updated', required: true, completed: false },
58
58
  ],
59
59
  priority: 'medium',
60
60
  },
@@ -62,4 +62,4 @@ export const TASK_TEMPLATES = [
62
62
 
63
63
  export function getTemplateById(id) {
64
64
  return TASK_TEMPLATES.find((t) => t.id === id)
65
- }
65
+ }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { buildStartMessage, obtainAgent } from './launcher.js'
15
15
  import { applyMove } from './routes.js'
16
+ import { defaultGitRunner } from './worktree.js'
16
17
 
17
18
  export const DEFAULT_COLUMN_ROLES = {
18
19
  backlog: 'spec',
@@ -22,7 +23,7 @@ export const DEFAULT_COLUMN_ROLES = {
22
23
  done: 'docs',
23
24
  }
24
25
 
25
- export const LABEL_ROLE_PATTERNS = [
26
+ const LABEL_ROLE_PATTERNS = [
26
27
  { role: 'spec', patterns: ['spec', 'specification', 'requirements', 'requirements-analysis', 'анализ', 'тз'] },
27
28
  { role: 'architecture', patterns: ['architecture', 'архитектура', 'contract'] },
28
29
  { role: 'ui_design', patterns: ['ui', 'ui_design', 'design', 'ux', 'дизайн'] },
@@ -228,7 +229,7 @@ export class WorkflowBridge {
228
229
  provider,
229
230
  model,
230
231
  mintSessionId,
231
- gitRunner,
232
+ gitRunner = defaultGitRunner,
232
233
  workspaceRegistry,
233
234
  claimAs,
234
235
  permission,
package/lib/worktree.js CHANGED
@@ -23,7 +23,7 @@ const execFileAsync = promisify(execFile)
23
23
  * Стандартный исполнитель git-команд.
24
24
  * Принимает аргументы массивом, исключая шелл-инъекции.
25
25
  */
26
- async function defaultGitRunner(args, cwd) {
26
+ export async function defaultGitRunner(args, cwd) {
27
27
  try {
28
28
  const { stdout, stderr } = await execFileAsync('git', args, {
29
29
  cwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-kanban",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Visual Kanban board for DeepSeek Harness: Gitea and GitHub-backed tasks, workflow columns, and a dedicated agent session per task.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",