dsh-task-ledger 0.0.96 → 0.0.98
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/client.js +3618 -116
- package/dist/client.js.map +1 -1
- package/dist/host/repositories/ledger.repository.d.ts +8 -2
- package/dist/host/repositories/ledger.repository.js +4 -1
- package/dist/host/repositories/records.d.ts +10 -2
- package/dist/host/repositories/records.js +11 -2
- package/dist/host/routes.js +1 -0
- package/dist/host/services/dates.d.ts +7 -2
- package/dist/host/services/dates.js +29 -6
- package/dist/host/services/history.service.js +1 -1
- package/dist/host/services/trend.service.d.ts +4 -1
- package/dist/host/services/trend.service.js +9 -1
- package/package.json +4 -1
- package/skill/README.md +2 -2
- package/skill/SKILL.md +43 -13
- package/skill/assets/worklog.md +5 -2
- package/skill/package.json +1 -1
- package/skill/references/state-model.md +6 -1
- package/skill/references/troubleshooting.md +5 -5
- package/skill/scripts/ledgerkit/actions/claim.py +7 -2
- package/skill/scripts/ledgerkit/actions/close_task.py +8 -0
- package/skill/scripts/ledgerkit/actions/handoff.py +5 -2
- package/skill/scripts/ledgerkit/actions/list_tasks.py +4 -1
- package/skill/scripts/ledgerkit/actions/log_progress.py +6 -2
- package/skill/scripts/ledgerkit/actions/new_task.py +7 -1
- package/skill/scripts/ledgerkit/actions/query_history.py +3 -1
- package/skill/scripts/ledgerkit/actions/release.py +6 -2
- package/skill/scripts/ledgerkit/actions/result.py +2 -0
- package/skill/scripts/ledgerkit/actions/show_task.py +32 -4
- package/skill/scripts/ledgerkit/actions/validate.py +6 -49
- package/skill/scripts/ledgerkit/core/identity.py +3 -3
- package/skill/scripts/ledgerkit/core/labels.py +16 -4
- package/skill/scripts/ledgerkit/core/time.py +2 -1
- package/skill/scripts/ledgerkit/core/worklog_md.py +216 -31
- package/skill/scripts/ledgerkit/shell/cli.py +24 -2
- package/skill/scripts/ledgerkit/shell/render.py +48 -4
- package/skill/scripts/ledgerkit/store/worklog_store.py +17 -5
|
@@ -18,6 +18,7 @@ from ..actions.result import (
|
|
|
18
18
|
ShowResult,
|
|
19
19
|
ValidationReport,
|
|
20
20
|
)
|
|
21
|
+
from ..core.identity import Identity
|
|
21
22
|
from ..core.time import fmt_local
|
|
22
23
|
|
|
23
24
|
|
|
@@ -51,6 +52,23 @@ def _json(data: Any) -> None:
|
|
|
51
52
|
sys.stdout.write(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
#: 任务 / 历史行里要转本机时区的时间字段(存盘一律 UTC,出门一律本机时区)。
|
|
56
|
+
TIME_FIELDS = ("created", "started", "updated", "finished")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def localize_times(data: dict) -> dict:
|
|
60
|
+
"""复制一份,把上面四个时间字段转成本机时区;其余字段原样。
|
|
61
|
+
|
|
62
|
+
**只动这四个**:任务号里嵌的时间戳、过程文档正文里的时间都不动(原文照旧)。
|
|
63
|
+
"""
|
|
64
|
+
out = dict(data)
|
|
65
|
+
for key in TIME_FIELDS:
|
|
66
|
+
value = out.get(key)
|
|
67
|
+
if value:
|
|
68
|
+
out[key] = fmt_local(value)
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
54
72
|
def result(res: Result, *, as_json: bool) -> None:
|
|
55
73
|
"""写动作:一律人话(`--json` 不挂写动作,传进来也按人话打)。"""
|
|
56
74
|
del as_json
|
|
@@ -71,7 +89,7 @@ def result(res: Result, *, as_json: bool) -> None:
|
|
|
71
89
|
|
|
72
90
|
def list_view(res: ListResult, q: ListQuery | None, *, as_json: bool) -> None:
|
|
73
91
|
if as_json:
|
|
74
|
-
_json({tid: task for tid, task in res.rows.items()})
|
|
92
|
+
_json({tid: localize_times(task) for tid, task in res.rows.items()})
|
|
75
93
|
return
|
|
76
94
|
if not res.table:
|
|
77
95
|
_out("没有符合条件的在办任务。")
|
|
@@ -84,11 +102,33 @@ def list_view(res: ListResult, q: ListQuery | None, *, as_json: bool) -> None:
|
|
|
84
102
|
_err(line)
|
|
85
103
|
|
|
86
104
|
|
|
105
|
+
def whoami_view(identity: Identity | None, *, as_json: bool) -> None:
|
|
106
|
+
"""本机身份:未初始化给 `uuid` / `name` 双 null(不是错误,调用方据此引导 init)。"""
|
|
107
|
+
if as_json:
|
|
108
|
+
_json(
|
|
109
|
+
{
|
|
110
|
+
"uuid": identity.uuid if identity else None,
|
|
111
|
+
"name": identity.name if identity else None,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
if identity is None:
|
|
116
|
+
_out("这台机器还没有初始化身份。")
|
|
117
|
+
_out(
|
|
118
|
+
" 处理:请用户在自己的终端运行 "
|
|
119
|
+
"`progress.py init --repo <工作区根>`,由 ta 亲自选择/新建并确认。"
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
_out(f"@{identity.label()} {identity.uuid}")
|
|
123
|
+
|
|
124
|
+
|
|
87
125
|
def show_view(res: ShowResult, *, as_json: bool) -> None:
|
|
88
126
|
if as_json:
|
|
89
127
|
_json(
|
|
90
128
|
{
|
|
91
|
-
"task": res.task,
|
|
129
|
+
"task": localize_times(res.task),
|
|
130
|
+
# 文档自述还是回退查在办:调用方据此知道字段来源(老文档 / 文档缺失)
|
|
131
|
+
"legacy": res.legacy,
|
|
92
132
|
"worklog": {
|
|
93
133
|
"path": res.worklog.path if res.worklog else None,
|
|
94
134
|
"text": res.worklog.text if res.worklog else None,
|
|
@@ -125,11 +165,15 @@ def history_view(res: HistoryResult, q: HistoryQuery | None, *, as_json: bool) -
|
|
|
125
165
|
_json({"count": res.count, "capped": res.capped})
|
|
126
166
|
return
|
|
127
167
|
if as_json:
|
|
168
|
+
filters = dict(res.filters)
|
|
169
|
+
for key in ("since", "until"):
|
|
170
|
+
if filters.get(key):
|
|
171
|
+
filters[key] = fmt_local(filters[key])
|
|
128
172
|
_json(
|
|
129
173
|
{
|
|
130
174
|
"count": res.count,
|
|
131
|
-
"filters":
|
|
132
|
-
"records":
|
|
175
|
+
"filters": filters,
|
|
176
|
+
"records": [localize_times(row) for row in res.records],
|
|
133
177
|
"warnings": list(res.warnings),
|
|
134
178
|
"capped": res.capped,
|
|
135
179
|
}
|
|
@@ -23,7 +23,8 @@ from .atomic import ensure_dir, write_text_atomic
|
|
|
23
23
|
|
|
24
24
|
FALLBACK_TEMPLATE = """# <id>:<title>
|
|
25
25
|
|
|
26
|
-
>
|
|
26
|
+
- **状态**:<status>
|
|
27
|
+
- **创建**:<created> · @<creator>
|
|
27
28
|
|
|
28
29
|
## 背景
|
|
29
30
|
|
|
@@ -131,9 +132,10 @@ class WorklogStore:
|
|
|
131
132
|
creator: str,
|
|
132
133
|
category: str | None = None,
|
|
133
134
|
tags: tuple[str, ...] = (),
|
|
135
|
+
status: worklog_md.StatusLine | None = None,
|
|
134
136
|
first_exec: str | None = None,
|
|
135
137
|
) -> Path:
|
|
136
|
-
"""
|
|
138
|
+
"""一次写完整篇(含状态行与首条执行记录)。返回绝对路径。"""
|
|
137
139
|
rel = worklog_rel(tid, created)
|
|
138
140
|
path = self.repo / to_repo_rel(rel)
|
|
139
141
|
ensure_dir(path.parent)
|
|
@@ -144,6 +146,7 @@ class WorklogStore:
|
|
|
144
146
|
creator=creator,
|
|
145
147
|
category=category,
|
|
146
148
|
tags=tags,
|
|
149
|
+
status=status,
|
|
147
150
|
template=self.template(),
|
|
148
151
|
)
|
|
149
152
|
if first_exec:
|
|
@@ -155,14 +158,23 @@ class WorklogStore:
|
|
|
155
158
|
"""写回「锚点编辑后的新全文」;不许用它替换前五节正文。"""
|
|
156
159
|
write_text_atomic(path, text)
|
|
157
160
|
|
|
158
|
-
def append_exec(
|
|
159
|
-
|
|
161
|
+
def append_exec(
|
|
162
|
+
self, path: Path, line: str, *, status: worklog_md.StatusLine | None = None
|
|
163
|
+
) -> bool:
|
|
164
|
+
"""追加一条执行记录,并在**同一次写盘**里顺带刷新头部状态行(`status` 给了才刷)。
|
|
165
|
+
|
|
166
|
+
写不进返回 False,**不抛**(调用方自己决定报不报)。状态行与执行记录必须一起落地:
|
|
167
|
+
分两写就会留下「记录写了、状态没刷」这种不报错的不一致。
|
|
168
|
+
"""
|
|
160
169
|
try:
|
|
161
170
|
text = path.read_text(encoding="utf-8-sig")
|
|
162
171
|
except (OSError, UnicodeDecodeError):
|
|
163
172
|
return False
|
|
164
173
|
try:
|
|
165
|
-
|
|
174
|
+
new = worklog_md.append_exec_log(text, line)
|
|
175
|
+
if status is not None:
|
|
176
|
+
new = worklog_md.rewrite_status_line(new, status)
|
|
177
|
+
write_text_atomic(path, new)
|
|
166
178
|
except Exception: # noqa: BLE001 —— 写不进是这条记录的失败,不该掀翻整条命令
|
|
167
179
|
return False
|
|
168
180
|
return True
|