dsh-task-ledger 0.0.17 → 0.0.18
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/dist/host/board.js +14 -7
- package/dist/host/types.d.ts +2 -0
- package/package.json +1 -1
- package/skill/package.json +1 -1
- package/skill/scripts/progress.py +118 -27
package/dist/host/board.js
CHANGED
|
@@ -165,17 +165,21 @@ export async function getHomeAggregate(workspaces) {
|
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
for (const entry of perWorkspace) {
|
|
168
|
+
const identity = entry.identity;
|
|
168
169
|
for (const task of entry.tasks) {
|
|
169
170
|
const created = dayOf(task.created);
|
|
170
171
|
if (created === null)
|
|
171
172
|
continue;
|
|
172
173
|
const week = ymd(mondayOf(created));
|
|
173
|
-
|
|
174
|
+
// 新增(我的)= 我创建的(creator);在办任务自带 creator 字段
|
|
175
|
+
const createdMine = identity !== null && (task.creator ?? null) === identity;
|
|
174
176
|
bump(bucketsAll, week, 'created');
|
|
175
|
-
if (
|
|
177
|
+
if (createdMine)
|
|
176
178
|
bump(bucketsMine, week, 'created');
|
|
179
|
+
// 未结(我的)= 我负责且未结案(owner)
|
|
180
|
+
const ownedMine = identity !== null && (task.owner ?? null) === identity;
|
|
177
181
|
bumpRemaining(bucketsAll, created, null);
|
|
178
|
-
if (
|
|
182
|
+
if (ownedMine)
|
|
179
183
|
bumpRemaining(bucketsMine, created, null);
|
|
180
184
|
}
|
|
181
185
|
for (const record of entry.history) {
|
|
@@ -183,19 +187,22 @@ export async function getHomeAggregate(workspaces) {
|
|
|
183
187
|
if (closed !== null) {
|
|
184
188
|
const week = ymd(mondayOf(closed));
|
|
185
189
|
bump(bucketsAll, week, 'done');
|
|
186
|
-
if (
|
|
190
|
+
if (identity !== null && (record.owner ?? null) === identity)
|
|
187
191
|
bump(bucketsMine, week, 'done');
|
|
188
192
|
}
|
|
189
193
|
const created = dayOf(record.created);
|
|
190
194
|
if (created !== null) {
|
|
191
195
|
const week = ymd(mondayOf(created));
|
|
192
196
|
bump(bucketsAll, week, 'created');
|
|
193
|
-
|
|
197
|
+
// 新增(我的)= 我创建的(creator);旧 8 列历史无 creator 列 ⇒ 兜底 owner
|
|
198
|
+
const createdMine = identity !== null && ((record.creator ?? null) === identity
|
|
199
|
+
|| ((record.creator ?? null) === null && (record.owner ?? null) === identity));
|
|
200
|
+
if (createdMine)
|
|
194
201
|
bump(bucketsMine, week, 'created');
|
|
195
202
|
}
|
|
196
|
-
const
|
|
203
|
+
const ownedMine = identity !== null && (record.owner ?? null) === identity;
|
|
197
204
|
bumpRemaining(bucketsAll, created, closed);
|
|
198
|
-
if (
|
|
205
|
+
if (ownedMine)
|
|
199
206
|
bumpRemaining(bucketsMine, created, closed);
|
|
200
207
|
}
|
|
201
208
|
}
|
package/dist/host/types.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface HistoryRecord {
|
|
|
40
40
|
category?: string | null;
|
|
41
41
|
tags?: string[] | null;
|
|
42
42
|
owner?: string;
|
|
43
|
+
/** 发起人(creator):任务的「创建者」,与 owner 区分(owner 可被 claim 改走)。历史 9 列新增;旧 8 列由 skill 回链 worklog 头部补。 */
|
|
44
|
+
creator?: string | null;
|
|
43
45
|
/** 「完成了什么」列。 */
|
|
44
46
|
summary?: string;
|
|
45
47
|
/** 过程文档链接列。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-task-ledger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Task ledger board for dsh (DeepSeek Harness): read-only board over task-ledger skill workspaces. The task-ledger skill is bundled — install the plugin, get the skill globally.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
package/skill/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-ledger",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Task-ledger skill for coding agents: per-workspace progress tracking (ledger/progress.json, history, worklog) driven by a CLI script. Standalone install for CodeBuddy and other agents; also bundled inside the dsh-task-ledger DSH plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skill",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
ledger/progress.json 在办任务的状态
|
|
6
6
|
ledger/worklog/<年月>/<日>-<时分>-<说明>-<随机码>.md
|
|
7
7
|
单个任务的过程(路径另记在状态表 worklog 字段)
|
|
8
|
-
ledger/history/<年月>.md 已结案任务,一行一条(
|
|
8
|
+
ledger/history/<年月>.md 已结案任务,一行一条(9 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / creator / 完成了什么 / 过程文档);
|
|
9
9
|
按结案时间分月归档。存量 ledger/PROGRESS-HISTORY.md 只读兼容、永不重写
|
|
10
10
|
|
|
11
11
|
只用标准库,无第三方依赖。
|
|
@@ -46,13 +46,12 @@ HISTORY_DIR = Path(
|
|
|
46
46
|
) # 新历史层:按结案时间分月,ledger/history/<YYYYMM>.md
|
|
47
47
|
WORKLOG_DIR = Path("ledger/worklog")
|
|
48
48
|
HISTORY_TITLE = "# 进度历史(已结案)"
|
|
49
|
-
#
|
|
49
|
+
# 9 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / creator / 完成了什么 / 过程文档。
|
|
50
50
|
# owner 独立成列 —— 按人检索是精确列比较,不靠文本切分。
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
51
|
+
# creator(2026-10-17 加):任务的「发起人」——与 owner 区分(owner 可被 claim 改走,
|
|
52
|
+
# 但「谁建的」不变)。看板「新增(我的)= 我创建的」要它;旧 8 列无此列,
|
|
53
|
+
# upgrade_history / cmd_history 回链 worklog 头部「创建」行补(与 created 同一来源,不丢)。
|
|
54
|
+
HISTORY_HEADER = "| 结案时间 | 创建时间 | 工作包 | 分类 | 标签 | owner | creator | 完成了什么 | 过程文档 |"
|
|
56
55
|
HISTORY_SEP = "|---|---|---|---|---|---|---|---|"
|
|
57
56
|
FROZEN_MARK = "✅ 完成封卷"
|
|
58
57
|
ID_RE = re.compile(r"[a-z0-9\u4e00-\u9fff][a-z0-9\u4e00-\u9fff-]*\Z")
|
|
@@ -403,6 +402,11 @@ def owner_cell(owner: str | None) -> str:
|
|
|
403
402
|
return f"@{owner}" if owner else ""
|
|
404
403
|
|
|
405
404
|
|
|
405
|
+
def creator_cell(creator: str | None) -> str:
|
|
406
|
+
"""creator 列展示值(与 owner_cell 同一 @ 约定)。"""
|
|
407
|
+
return f"@{creator}" if creator else ""
|
|
408
|
+
|
|
409
|
+
|
|
406
410
|
def history_line(
|
|
407
411
|
finished: str,
|
|
408
412
|
created: str | None,
|
|
@@ -412,17 +416,19 @@ def history_line(
|
|
|
412
416
|
log_rel: str,
|
|
413
417
|
category: str | None = None,
|
|
414
418
|
tags: list[str] | None = None,
|
|
419
|
+
creator: str | None = None,
|
|
415
420
|
) -> str:
|
|
416
|
-
"""历史层的一行(
|
|
421
|
+
"""历史层的一行(9 列:结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / creator / 完成了什么 / 过程文档)。
|
|
417
422
|
|
|
418
|
-
close 专用,避免两处格式漂移。owner 是独立列、单独过 esc_cell ——
|
|
419
|
-
|
|
423
|
+
close 专用,避免两处格式漂移。owner / creator 是独立列、单独过 esc_cell ——
|
|
424
|
+
它们来自 git config user.name,含 `|` 也不稀奇。分类/标签由 close 传 task 终值。
|
|
420
425
|
"""
|
|
421
426
|
cat_cell = esc_cell(category or "")
|
|
422
427
|
tag_cell = esc_cell("、".join(tags) if tags else "")
|
|
423
428
|
return (
|
|
424
429
|
f"| {finished} | {created or ''} | {esc_cell(title)} | {cat_cell} | {tag_cell} "
|
|
425
|
-
f"| {esc_cell(owner_cell(owner))} | {esc_cell(
|
|
430
|
+
f"| {esc_cell(owner_cell(owner))} | {esc_cell(creator_cell(creator))} "
|
|
431
|
+
f"| {esc_cell(summary)} | [{log_rel}]({log_rel}) |\n"
|
|
426
432
|
)
|
|
427
433
|
|
|
428
434
|
|
|
@@ -436,12 +442,13 @@ def history_line_for_cols(
|
|
|
436
442
|
log_rel: str,
|
|
437
443
|
category: str | None = None,
|
|
438
444
|
tags: list[str] | None = None,
|
|
445
|
+
creator: str | None = None,
|
|
439
446
|
) -> str:
|
|
440
447
|
"""「历史层有无法解析的行、不敢整体升级」时,按现有列数追加一行,避免列错位。
|
|
441
448
|
|
|
442
449
|
GFM 按首行列数渲染,列数写错会被静默丢弃 / 错位。cols<=4 ⇒ 旧 4 列(owner 缝在
|
|
443
450
|
工作包末尾);5 ⇒ 无创建时间列;6 ⇒ 无分类/标签列(存量改造前的最后形态);
|
|
444
|
-
否则
|
|
451
|
+
否则 9 列。分类/标签/creator 只在 9 列里出现 —— 少列的文件硬塞会把整行挤歪。
|
|
445
452
|
"""
|
|
446
453
|
if cols <= 4:
|
|
447
454
|
who = f" @{owner}" if owner else ""
|
|
@@ -460,7 +467,7 @@ def history_line_for_cols(
|
|
|
460
467
|
f"| {esc_cell(owner_cell(owner))} | {esc_cell(summary)} | [{log_rel}]({log_rel}) |\n"
|
|
461
468
|
)
|
|
462
469
|
return history_line(
|
|
463
|
-
finished, created, title, owner, summary, log_rel, category, tags
|
|
470
|
+
finished, created, title, owner, summary, log_rel, category, tags, creator
|
|
464
471
|
)
|
|
465
472
|
|
|
466
473
|
|
|
@@ -509,11 +516,13 @@ def _looks_like_header(cells: list[str]) -> bool:
|
|
|
509
516
|
def parse_history(text: str) -> tuple[list[dict[str, Any]], list[str]]:
|
|
510
517
|
"""解析历史层,返回 (records, warnings)。
|
|
511
518
|
|
|
512
|
-
多格式兼容:
|
|
519
|
+
多格式兼容:9 列 = v9(结案时间 / 创建时间 / 工作包 / 分类 / 标签 / owner / creator / …);
|
|
520
|
+
8 列 = v8(存量改造后、2026-10-17 前,无 creator);6 列 = v6
|
|
513
521
|
(存量改造前最后形态,无分类/标签);5 列且第 2 列是时间 = 旧 ledger 格式
|
|
514
522
|
(有创建时间列、无 owner 列);5 列 = 无创建时间列;4 列 = 最旧(owner 缝在工作包末尾)。
|
|
515
523
|
**逐行按自己的列数判别**,表头只用来跳过 —— 旧文件追加新列
|
|
516
|
-
后是混排态,按表头判列数会切错。坏行(列数不在 4/5/6/8、时间列不合法)不中断:跳过 + 警告。
|
|
524
|
+
后是混排态,按表头判列数会切错。坏行(列数不在 4/5/6/8/9、时间列不合法)不中断:跳过 + 警告。
|
|
525
|
+
8 列行无 creator 列 ⇒ creator 留 None,cmd_history 回链 worklog 头部「创建」行补(与 created 同源)。
|
|
517
526
|
"""
|
|
518
527
|
records: list[dict[str, Any]] = []
|
|
519
528
|
warnings: list[str] = []
|
|
@@ -526,13 +535,31 @@ def parse_history(text: str) -> tuple[list[dict[str, Any]], list[str]]:
|
|
|
526
535
|
created = ""
|
|
527
536
|
category: str | None = None
|
|
528
537
|
tags: list[str] = []
|
|
529
|
-
|
|
538
|
+
creator: str | None = None
|
|
539
|
+
if len(cells) == 9:
|
|
540
|
+
(
|
|
541
|
+
finished,
|
|
542
|
+
created,
|
|
543
|
+
workpackage,
|
|
544
|
+
category,
|
|
545
|
+
tag_cell,
|
|
546
|
+
owner,
|
|
547
|
+
creator,
|
|
548
|
+
summary,
|
|
549
|
+
doc,
|
|
550
|
+
) = cells
|
|
551
|
+
fmt = "v9"
|
|
552
|
+
category = category.strip() or None
|
|
553
|
+
tags = [t.strip() for t in re.split(r"[,,、]", tag_cell) if t.strip()]
|
|
554
|
+
creator = creator.strip().removeprefix("@") or None
|
|
555
|
+
elif len(cells) == 8:
|
|
530
556
|
finished, created, workpackage, category, tag_cell, owner, summary, doc = (
|
|
531
557
|
cells
|
|
532
558
|
)
|
|
533
559
|
fmt = "v8"
|
|
534
560
|
category = category.strip() or None
|
|
535
561
|
tags = [t.strip() for t in re.split(r"[,,、]", tag_cell) if t.strip()]
|
|
562
|
+
# 旧 8 列无 creator 列:留 None,cmd_history 回链 worklog 头部补(不丢)
|
|
536
563
|
elif len(cells) == 6:
|
|
537
564
|
finished, created, workpackage, owner, summary, doc = cells
|
|
538
565
|
fmt = "v6"
|
|
@@ -555,7 +582,7 @@ def parse_history(text: str) -> tuple[list[dict[str, Any]], list[str]]:
|
|
|
555
582
|
owner = ""
|
|
556
583
|
fmt = "v4"
|
|
557
584
|
else:
|
|
558
|
-
warnings.append(f"第 {lineno} 行列数不是 4 / 5 / 6 / 8,已跳过")
|
|
585
|
+
warnings.append(f"第 {lineno} 行列数不是 4 / 5 / 6 / 8 / 9,已跳过")
|
|
559
586
|
continue
|
|
560
587
|
if not TIME_RE.fullmatch(finished):
|
|
561
588
|
warnings.append(f"第 {lineno} 行时间列 {finished!r} 不合法,已跳过")
|
|
@@ -572,6 +599,7 @@ def parse_history(text: str) -> tuple[list[dict[str, Any]], list[str]]:
|
|
|
572
599
|
"category": category,
|
|
573
600
|
"tags": tags,
|
|
574
601
|
"owner": own or None,
|
|
602
|
+
"creator": creator,
|
|
575
603
|
"summary": summary,
|
|
576
604
|
"log": log or None,
|
|
577
605
|
"fmt": fmt,
|
|
@@ -581,11 +609,14 @@ def parse_history(text: str) -> tuple[list[dict[str, Any]], list[str]]:
|
|
|
581
609
|
return records, warnings
|
|
582
610
|
|
|
583
611
|
|
|
584
|
-
# worklog 头部「创建」行:`> **创建**:2026-10-06 00:00
|
|
612
|
+
# worklog 头部「创建」行:`> **创建**:2026-10-06 00:00(@某人)`。
|
|
613
|
+
# 时间给旧历史行回填创建时间;括号内 @某人 即 creator(发起人)。
|
|
585
614
|
WORKLOG_CREATED_RE = re.compile(
|
|
586
615
|
r"^\s*>\s*\*\*创建\*\*\s*[::]\s*"
|
|
587
616
|
r"(\d{4}-\d{2}-\d{2}(?: +\d{1,2}:\d{1,2}(?::\d{1,2})?)?)"
|
|
588
617
|
)
|
|
618
|
+
# 同一行的发起人(@某人)——creator 列来源。
|
|
619
|
+
WORKLOG_CREATOR_RE = re.compile(r"^\s*>\s*\*\*创建\*\*\s*[::].*?(@(.+?))")
|
|
589
620
|
|
|
590
621
|
|
|
591
622
|
def created_for_history(log_cell: str, history_path: Path) -> str | None:
|
|
@@ -611,6 +642,28 @@ def created_for_history(log_cell: str, history_path: Path) -> str | None:
|
|
|
611
642
|
return None
|
|
612
643
|
|
|
613
644
|
|
|
645
|
+
def creator_for_history(log_cell: str | None, history_path: Path) -> str | None:
|
|
646
|
+
"""顺着历史行的文档链接找到 worklog,读它头部「创建」行的发起人(@某人);找不到返回 None。
|
|
647
|
+
|
|
648
|
+
旧 8 列历史行没有 creator 列,迁移 / 只读回填都靠它 —— 与 created 同源,不丢数据。
|
|
649
|
+
"""
|
|
650
|
+
if not log_cell:
|
|
651
|
+
return None
|
|
652
|
+
m = re.fullmatch(r"\[[^\]]*\]\(([^)]*)\)", log_cell.strip())
|
|
653
|
+
rel = (m.group(1) if m else log_cell).strip()
|
|
654
|
+
if not rel:
|
|
655
|
+
return None
|
|
656
|
+
try:
|
|
657
|
+
text = (history_path.parent / rel).read_text(encoding="utf-8")
|
|
658
|
+
except (OSError, UnicodeDecodeError):
|
|
659
|
+
return None
|
|
660
|
+
for ln in text.splitlines():
|
|
661
|
+
mm = WORKLOG_CREATOR_RE.match(ln)
|
|
662
|
+
if mm:
|
|
663
|
+
return mm.group(1).strip()
|
|
664
|
+
return None
|
|
665
|
+
|
|
666
|
+
|
|
614
667
|
def labels_for_log(
|
|
615
668
|
log_cell: str | None, history_path: Path
|
|
616
669
|
) -> tuple[str | None, list[str]]:
|
|
@@ -633,11 +686,13 @@ def labels_for_log(
|
|
|
633
686
|
|
|
634
687
|
|
|
635
688
|
def upgrade_history(text: str, history_path: Path) -> tuple[str, int] | None:
|
|
636
|
-
"""旧格式(4 / 5 / 6 列)→
|
|
689
|
+
"""旧格式(4 / 5 / 6 / 8 列)→ v9(9 列)。全部数据行可解析才返回 (新文本, 转换行数),否则 None。
|
|
637
690
|
|
|
638
691
|
None 的语义是「不敢动」:调用方必须退回按原列数追加并明示,绝不半升级、绝不猜。
|
|
639
692
|
分类/标签列:回链每行的 worklog 头部读「分类/标签」行(读不到留空,不编造)——
|
|
640
693
|
与 created 的回填同一模式,依赖 Step 2 已给存量 worklog 补行。
|
|
694
|
+
creator 列(2026-10-17 加):旧 8 列无此列,同样回链 worklog 头部「创建」行的
|
|
695
|
+
(@发起人)补 —— **与 created 同源,迁移不丢发起人**。读不到留空。
|
|
641
696
|
缺创建时间的旧行:先读它链接的 worklog 头部「创建」行,找不到用升级时刻 —— 不允许留空。
|
|
642
697
|
5 列有两种(判别与 parse_history 一致,靠「第 2 列是不是时间」):旧 ledger 格式
|
|
643
698
|
(结案 / 创建 / 工作包 / 摘要 / 文档,无 owner)行内自带创建时间,优先用它;
|
|
@@ -662,13 +717,33 @@ def upgrade_history(text: str, history_path: Path) -> tuple[str, int] | None:
|
|
|
662
717
|
if _looks_like_header(cells):
|
|
663
718
|
out.append(HISTORY_HEADER)
|
|
664
719
|
continue
|
|
665
|
-
if len(cells) ==
|
|
666
|
-
#
|
|
720
|
+
if len(cells) == 9:
|
|
721
|
+
# v9 行原样回写:已含 creator
|
|
667
722
|
if time_key(cells[0]) is None:
|
|
668
723
|
return None
|
|
669
724
|
out.append("| " + " | ".join(cells) + " |")
|
|
670
725
|
continue
|
|
671
726
|
explicit_created: str | None = None
|
|
727
|
+
creator: str | None = None
|
|
728
|
+
if len(cells) == 8:
|
|
729
|
+
# v8 → v9:补 creator 列(回链 worklog 头部「创建」行的 @发起人;读不到留空不编造)
|
|
730
|
+
finished, created, workpackage, category, tag_cell, owner, summary, doc = (
|
|
731
|
+
cells
|
|
732
|
+
)
|
|
733
|
+
if time_key(finished) is None:
|
|
734
|
+
return None
|
|
735
|
+
created_key = time_key(created.strip())
|
|
736
|
+
cat = category.strip() or None
|
|
737
|
+
tags = [t.strip() for t in re.split(r"[,,、]", tag_cell) if t.strip()]
|
|
738
|
+
creator = creator_for_history(doc, history_path) or ""
|
|
739
|
+
out.append(
|
|
740
|
+
f"| {finished} | {created_key or ''} | {esc_cell(workpackage)} "
|
|
741
|
+
f"| {esc_cell(cat or '')} | {esc_cell('、'.join(tags) if tags else '')} "
|
|
742
|
+
f"| {esc_cell(owner_cell(owner.lstrip('@')))} | {esc_cell(creator_cell(creator))} "
|
|
743
|
+
f"| {esc_cell(summary)} | {doc} |"
|
|
744
|
+
)
|
|
745
|
+
converted += 1
|
|
746
|
+
continue
|
|
672
747
|
if len(cells) == 6:
|
|
673
748
|
finished, created, workpackage, owner, summary, doc = cells
|
|
674
749
|
# v6 升级:owner 单元格已是展示格式(带 @),剥掉再走 owner_cell,否则升级出 @@
|
|
@@ -698,10 +773,12 @@ def upgrade_history(text: str, history_path: Path) -> tuple[str, int] | None:
|
|
|
698
773
|
return None
|
|
699
774
|
created = explicit_created or created_for_history(doc, history_path) or fallback
|
|
700
775
|
cat, tags = labels_for_log(doc, history_path)
|
|
776
|
+
creator = creator_for_history(doc, history_path) or ""
|
|
701
777
|
out.append(
|
|
702
778
|
f"| {finished_key} | {created} | {esc_cell(workpackage)} "
|
|
703
779
|
f"| {esc_cell(cat or '')} | {esc_cell('、'.join(tags) if tags else '')} "
|
|
704
|
-
f"| {esc_cell(owner_cell(owner))} | {esc_cell(
|
|
780
|
+
f"| {esc_cell(owner_cell(owner))} | {esc_cell(creator_cell(creator))} "
|
|
781
|
+
f"| {esc_cell(summary)} | {doc} |"
|
|
705
782
|
)
|
|
706
783
|
converted += 1
|
|
707
784
|
return "\n".join(out) + "\n", converted
|
|
@@ -796,18 +873,27 @@ def append_history_line(
|
|
|
796
873
|
log_rel: str,
|
|
797
874
|
category: str | None = None,
|
|
798
875
|
tags: list[str] | None = None,
|
|
876
|
+
creator: str | None = None,
|
|
799
877
|
) -> tuple[str, int]:
|
|
800
878
|
"""把一行历史追加到指定历史文件,返回 (新全文, 升级行数)。
|
|
801
879
|
|
|
802
|
-
读目标 → upgrade_history → 解析不了就按现有列数追加(绝不把
|
|
803
|
-
→ 文件不存在则带
|
|
804
|
-
|
|
880
|
+
读目标 → upgrade_history → 解析不了就按现有列数追加(绝不把 9 列写进少列表头)
|
|
881
|
+
→ 文件不存在则带 9 列表头新建。close 专用:「行落哪个月」之外,
|
|
882
|
+
历史行的落盘逻辑只此一处。分类/标签/creator 由 close 传 task 终值。
|
|
805
883
|
"""
|
|
806
884
|
if not history_path.exists():
|
|
807
885
|
return (
|
|
808
886
|
f"{HISTORY_TITLE}\n\n{HISTORY_HEADER}\n{HISTORY_SEP}\n"
|
|
809
887
|
+ history_line(
|
|
810
|
-
finished,
|
|
888
|
+
finished,
|
|
889
|
+
created,
|
|
890
|
+
title,
|
|
891
|
+
owner,
|
|
892
|
+
summary,
|
|
893
|
+
log_rel,
|
|
894
|
+
category,
|
|
895
|
+
tags,
|
|
896
|
+
creator,
|
|
811
897
|
),
|
|
812
898
|
0,
|
|
813
899
|
)
|
|
@@ -836,6 +922,7 @@ def append_history_line(
|
|
|
836
922
|
log_rel,
|
|
837
923
|
category,
|
|
838
924
|
tags,
|
|
925
|
+
creator,
|
|
839
926
|
),
|
|
840
927
|
0,
|
|
841
928
|
)
|
|
@@ -843,7 +930,7 @@ def append_history_line(
|
|
|
843
930
|
return (
|
|
844
931
|
new_text
|
|
845
932
|
+ history_line(
|
|
846
|
-
finished, created, title, owner, summary, log_rel, category, tags
|
|
933
|
+
finished, created, title, owner, summary, log_rel, category, tags, creator
|
|
847
934
|
),
|
|
848
935
|
n,
|
|
849
936
|
)
|
|
@@ -1308,6 +1395,7 @@ def cmd_close(args: argparse.Namespace) -> None:
|
|
|
1308
1395
|
log_rel,
|
|
1309
1396
|
final_cat,
|
|
1310
1397
|
final_tags,
|
|
1398
|
+
task.get("creator"),
|
|
1311
1399
|
)
|
|
1312
1400
|
|
|
1313
1401
|
if log_path.exists():
|
|
@@ -1788,6 +1876,9 @@ def cmd_history(args: argparse.Namespace) -> None:
|
|
|
1788
1876
|
# 8 列行自带分类/标签;旧 4/5/6 列行没有,回链读 worklog 头部兜底
|
|
1789
1877
|
if not r.get("category") and not r.get("tags"):
|
|
1790
1878
|
r["category"], r["tags"] = labels_for_log(r["log"], hp)
|
|
1879
|
+
# v8 行(无 creator 列)回链 worklog 头部「创建」行取发起人,不丢
|
|
1880
|
+
if not r.get("creator"):
|
|
1881
|
+
r["creator"] = creator_for_history(r["log"], hp)
|
|
1791
1882
|
records.extend(recs)
|
|
1792
1883
|
warnings.extend(f"{rel} {w}" for w in warns)
|
|
1793
1884
|
|