From 7d803245a50247b2a7e30d60a41a759da14e3196 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 31 Aug 2026 11:07:16 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(overwrite):=20=E5=8F=AA=E6=9C=89?= =?UTF-8?q?=E9=A0=81=E9=9D=A2=E7=9C=9F=E7=9A=84=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=89=8D=E5=BB=BA=E6=96=B0=E9=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - What:worklog、learn、report 三支技能共六處讀取分流改寫,只有結束碼 4 才建新頁; 結束碼 7 與 8 一律中止,一個字都不寫。三份目錄頁樣板補上寫入語意,明寫禁止整頁覆蓋、 不得改動別人的列。usage-stats.sh 的參數護欄改成明確回傳結束碼 2。 - Why:原本把「讀失敗」與「頁面不存在」當成同一件事。金鑰失效時讀取回 7,技能卻讀成 「這頁還沒有」,於是照樣板建一份新頁蓋回去。整份工作日誌會被這一筆條目取代, 既有教訓會被清成空表,報表則產出一份說「這段期間沒有工作」的假數字。 這些內容只活在 wiki 上,蓋掉就救不回來,所以這是本輪最要緊的一項。 - How:每一處讀取都先看結束碼再決定動作,並在技能文件裡列成表格: 0 接在既有內容後面附加,4 才從樣板建頁,5 缺網址就中止,7 金鑰或權限問題就中止, 8 其他 API 失敗就中止。目錄頁一律先讀整頁再改那一列。 stats 也補上分流,參數錯誤不再被讀成「零次」。 - Who:worklog、learn、report、stats 四支技能的 wiki 讀寫與統計輸出。 --- skills/learn/SKILL.md | 30 +++++++++--- skills/report/SKILL.md | 88 ++++++++++++++++++++++++------------ skills/stats/SKILL.md | 10 +++- skills/worklog/SKILL.md | 53 +++++++++++++++++----- templates/learn-contents.md | 2 + templates/log-contents.md | 2 + templates/report-contents.md | 2 + tools/usage-stats.sh | 16 +++++-- 8 files changed, 152 insertions(+), 51 deletions(-) diff --git a/skills/learn/SKILL.md b/skills/learn/SKILL.md index 223efbc..e82b97c 100644 --- a/skills/learn/SKILL.md +++ b/skills/learn/SKILL.md @@ -10,8 +10,8 @@ Close the loop on skill runs: record what a run taught you, consult it before th ## Target pages - Directory page: `LEARN_CONTENTS`. Content page: `LEARN_{HASH}`, one page per repository. -- Compute `{HASH}` from `{owner}/{repo}` with `jsc-gitea/tools/hash-id`. -- Wiki repo resolution: `JSC_WIKI_REPO_LEARN` first, then `JSC_WIKI_REPO`. Inspect the inherited shell environment variables first; ask the user per the `jsc-ask:ask` rules only when neither resolves. Never borrow another type's `JSC_WIKI_REPO_{TYPE}`. +- Compute `{HASH}` from `{owner}/{repo}` with `jsc-gitea/tools/hash-id`. Exit 1 means no SHA-1 helper on this machine: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash. +- Wiki repo: run `jsc-gitea/tools/gitea.sh wiki-repo LEARN`. Exit 3 hands the question to `jsc-gitea:wiki`, which owns the resolution order and the wording; exit 2 means the type argument was misspelled, so fix it and rerun. - All wiki reads and writes go through `jsc-gitea:wiki`. ## Mode: record @@ -31,14 +31,32 @@ Run after a skill run that produced a reusable lesson. 2. Resolve `{owner}/{repo}` from `git remote get-url origin` and compute `{HASH}` with `jsc-gitea/tools/hash-id`. Done when the page name `LEARN_{HASH}` is known. 3. Write the entry. This step MUST run as a sub agent; the main agent only confirms the write succeeded. - - Read `LEARN_{HASH}` via `jsc-gitea:wiki`. If it does not exist, create it from `templates/learn-page.md`; otherwise APPEND the new row at the end of the table. Never overwrite existing rows. - - Update `LEARN_CONTENTS` in the same pass (apply `templates/learn-contents.md`; add the repo row if missing, otherwise refresh its 最後更新時間). - - Done when the sub agent reports both pages written and the main agent has confirmed the row exists on `LEARN_{HASH}`. + - Read `LEARN_{HASH}` via `jsc-gitea:wiki` and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet**; creating the page from the template on any other code appends this one row to a blank table and drops every lesson already recorded. + + | Exit | Do | + | --- | --- | + | 0 | The table is in hand. APPEND the new row at the end of it and overwrite no existing row. Never rebuild the page from the template on this code | + | 4 | The page really is absent. Create it from `templates/learn-page.md`, then add the row | + | 7 | The token is invalid or lacks permission, so the old rows are unknown. Stop and report the token problem, and create no page | + | 8 | Some other API failure. Stop and report that status, and create no page | + + - Update `LEARN_CONTENTS` in the same pass (apply `templates/learn-contents.md`; add the repo row if missing, otherwise refresh its 最後更新時間), branching on its read exactly as above: only exit 4 creates the directory page from the template, while 7 and 8 stop the run instead of rebuilding a directory whose other repos' rows were never read. Touch no row that belongs to another repository. + - A failed `jsc-gitea:wiki` write on either page: retry once. Still failing, stop and report which page was not written (`LEARN_{HASH}` or `LEARN_CONTENTS`) together with the row content that was meant to go in, so the lesson is not lost. Never report a page as written when it was not. + - Done when the sub agent reports both pages written, the main agent has confirmed the row exists on `LEARN_{HASH}`, and the rows that were there before are still there. ## Mode: consult Run before a skill run, to apply past lessons. 1. Resolve `{owner}/{repo}` and compute `{HASH}` as in record mode. Done when `LEARN_{HASH}` is known. -2. Read `LEARN_CONTENTS` and the repo's `LEARN_{HASH}` via `jsc-gitea:wiki`. When either page is missing (exit 4), report 「無教訓紀錄」 and let the caller proceed. Done when both pages are read or reported missing. +2. Read `LEARN_CONTENTS` and the repo's `LEARN_{HASH}` via `jsc-gitea:wiki`, and branch on the exit code `jsc-gitea/tools/gitea.sh` returned. Only 4 means the page is absent; every other failure code means the read never happened, so an empty page must never be inferred from it. + + | Exit | Do | + | --- | --- | + | 4 | The page does not exist. Report 「無教訓紀錄」 for that page and let the caller proceed | + | 5 | The page carries no `html_url`. Stop and report it; never assemble the URL by hand and never read it as an empty page | + | 7 | The Gitea token is invalid or lacks permission (HTTP 401/403). Stop and report that the token has to be fixed. Reading this as 「無教訓紀錄」 is exactly the misread `gitea.sh` separates 7 from 4 to prevent | + | 8 | Some other API failure, with the HTTP status in the message. Stop and report that status; retry only after the API is back | + + Done when both pages are read, or reported missing under exit 4, or the run stopped on 5, 7 or 8. 3. Surface every row whose 技能 matches the skill about to run, and summarize each matched 下次做法 for the caller to apply. Done when the matched rows (or 「無相符教訓」) are reported. diff --git a/skills/report/SKILL.md b/skills/report/SKILL.md index 48839cd..5d80b81 100644 --- a/skills/report/SKILL.md +++ b/skills/report/SKILL.md @@ -11,49 +11,81 @@ Reading and aggregating the log pages **MUST run as a sub agent**: it reads ever Ask for the period per the `jsc-ask:ask` rules when the caller did not name one: `daily`, `weekly`, `monthly`, `yearly`. Each option states what it covers. -Run `tools/report-range.sh {period} [yyyy-MM-dd]` — it prints `{start}{end}{label}{period}`, both dates inclusive. The base date defaults to today; pass one to re-run an earlier period. +Run `tools/report-range.sh {period} [yyyy-MM-dd]`. It prints `{start}{end}{label}{period}`, both dates inclusive. The base date defaults to today; pass one to re-run an earlier period. -Never work the dates out by hand. Week boundaries and month lengths are exactly where a hand-rolled range quietly loses a day. +| Exit | Meaning | Do | +| --- | --- | --- | +| 0 | Range printed | Read start, end and label out of the four fields | +| 2 | Period name or base date rejected | Ask per the `jsc-ask:ask` rules which of the four periods was meant, or fix the `yyyy-MM-dd` base date, then rerun | +| 4 | This machine's `date` does no date arithmetic | Stop and report that the range cannot be computed here. Never work the dates out by hand: week boundaries and month lengths are exactly where a hand-rolled range quietly loses a day | Done when start, end and label are known. -## 2. Template +## 2. Resolve and collect -Run `tools/report-template.sh resolve {period}` from the working directory. It prints `{path}{project|skill}`. +Run these four lines of work in parallel — none of them consumes another's output, and the log pages are the slow one: -`project` means the working directory holds `.jsc/templates/report-{period}.md` and that file wins. Say which source was used in the final report — the same period rendered from two different templates has to be traceable to the file that shaped it. +1. **Template.** `tools/report-template.sh resolve {period}` from the working directory prints `{path}{project|skill}`. +2. **Log pages.** `jsc-gitea/tools/gitea.sh wiki-repo LOG`, then read `LOG_CONTENTS` through `jsc-gitea:wiki`, then read **every** log page it lists, one sub agent per page. +3. **Lessons (yearly only).** `gitea.sh wiki-repo LEARN`, then read `LEARN_CONTENTS` through `jsc-gitea:wiki` for the 全年教訓 section. Resolve `JSC_WIKI_REPO_LEARN` on its own: the LOG repo resolved in line 2 never stands in for it, and LOG and LEARN pages routinely live in different wiki repos. Other periods skip this line. +4. **Report repo.** `gitea.sh wiki-repo REPORT`, so step 3 has its target ready. + +Exit branches for the external calls above: + +| Call | Exit | Do | +| --- | --- | --- | +| `report-template.sh resolve` | 0 | Use the path; name the `project` or `skill` source in the final report. `project` means the working directory holds `.jsc/templates/report-{period}.md` and that file wins — the same period rendered from two templates has to be traceable to the file that shaped it | +| `report-template.sh resolve` | 2 | Period name or start directory rejected. Rerun from the working directory with the period from step 1 | +| `report-template.sh resolve` | 3 | Neither the project copy nor the skill's own copy exists. Stop and report that `templates/report-{period}.md` is missing from the plugin; do not invent a layout | +| `gitea.sh wiki-repo LOG` | 3 | Stop and report that no wiki repo is configured for LOG, naming `JSC_WIKI_REPO_LOG` and `JSC_WIKI_REPO`. Ask per the `jsc-ask:ask` rules, then rerun. Without log pages there is nothing to summarise | +| `gitea.sh wiki-repo LEARN` | 3 | Fill the 全年教訓 section with 無 and say the LEARN wiki repo is unset. The rest of the yearly report still stands | +| `gitea.sh wiki-repo REPORT` | 3 | Carry on collecting; step 3 handles the skipped write | +| `gitea.sh wiki-repo` any type | 2 | The page type was misspelled. Fix the argument and rerun | +| `jsc-gitea:wiki` read | 4 | The page is genuinely absent. **Only this code** lets `LOG_CONTENTS` or a listed log page count as zero entries; name it in the close-out | +| `jsc-gitea:wiki` read | 7 | The token is invalid or lacks permission. Stop and report the token problem. Counting this as zero entries publishes a report that says a period held no work when the work is sitting on a page nobody managed to read | +| `jsc-gitea:wiki` read | 8 | Some other API failure. Stop and report that status; a page that failed to load must never be counted as an empty page | + +Then aggregate. Save the collected page contents to files and run: + +`tools/log-aggregate.sh {start} {end} {page file} ...` + +It prints `ENTRIES=`, `REPOS=`, one `REPO=` line per repository, `ELAPSED_MINUTES=`, `ELAPSED_ENTRIES=`, `ELAPSED_MISSING=`, one `TOKEN=` line per CLI, `TOKEN_MISSING=` and one `STATUS=` line per status. It enforces the no-estimate rule in code: an entry with no 花費時間 stays out of the total and lands in `ELAPSED_MISSING`, and a range where nothing carried a time prints `ELAPSED_MINUTES=無資料` rather than `0`. + +| Exit | Meaning | Do | +| --- | --- | --- | +| 0 | Aggregates printed | Carry the printed values into the template unchanged. Never recompute or round them by hand | +| 2 | Dates rejected, or start later than end | Rerun with the step 1 values | +| 3 | Zero entries inside the range; the zero-filled aggregate is still printed | Produce the report anyway, with counts of 0 and a line naming the empty range. A silent "no report" cannot be told apart from a failure | +| 4 | A page file is unreadable | Stop and report which file, rather than reporting a smaller total | + +Blockers and unfinished work packages come from the 任務狀態 and 遇到的困難與解決方式 parts of the surviving entries; the sub agent lists them verbatim. + +Done when the template path, its source, the aggregate lines and the blocker list are all in hand. + +## 3. Write Follow the template's headings and tables exactly, including ones with no data: an empty section stated as 無 is information, a silently dropped section is not. -Done when the template path and its source are known. - -## 3. Collect - -Read `LOG_CONTENTS` through `jsc-gitea:wiki` (repo from `jsc-gitea/tools/gitea.sh wiki-repo LOG`), then read every log page it lists. Entries start with `## {yyyy-MM-dd HH:mm}`; keep those whose date falls within start and end. - -Aggregate from the entry tables: entry count, distinct repositories, elapsed time, token usage per CLI, task status counts, blockers, unfinished work packages. Sum only what the entries state — an entry with no 花費時間 stays out of the total and is reported as 無資料 rather than estimated. - -A yearly report also reads `LEARN_CONTENTS` for its 全年教訓 section; other periods skip it. - -Zero entries in range → produce the report anyway, with counts of 0 and a line saying which range came back empty. A silent "no report" cannot be told apart from a failure. - -Done when every log page in the directory has been read and the aggregates are computed. - -## 4. Write - Write through `jsc-gitea:wiki`: -- Repo: `gitea.sh wiki-repo REPORT`. +- Repo: the REPORT repo from step 2, line 4. - Page: `REPORT_` plus `gitea.sh hash-id "{owner}/{repo}/{period}"`, where `{owner}/{repo}` is the REPORT wiki repo. Year, month, week and day each get their own page. +- Read the page first and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet** and may be built from scratch. On exit 0 the existing sections are in hand, so append into them. On exit 7 the token is invalid or lacks permission, and on exit 8 the API failed some other way: both leave the earlier periods unknown, so stop, report the status and write nothing — a page rebuilt on top of an unread read loses every period already on it. - Append this period as a new section, newest first. Rerunning the same period replaces that period's section only, leaving the other periods untouched. -- Refresh the page's row in `REPORT_CONTENTS` from `templates/report-contents.md`. +- Refresh the page's row in `REPORT_CONTENTS` from `templates/report-contents.md`: add the row if missing, otherwise refresh its 最新一期、期數 and 最後更新. Its read branches the same way — only exit 4 creates the directory page from the template, while 7 and 8 stop the run. Touch no row that belongs to another report page. -`wiki-repo` exit 3 → print the finished report and say the write was skipped because no wiki repo is configured for REPORT. The report itself is still the deliverable. +| Call | Exit | Do | +| --- | --- | --- | +| `gitea.sh wiki-repo REPORT` | 3 | Print the finished report and say the write was skipped because no wiki repo is configured for REPORT. The report itself is still the deliverable | +| `gitea.sh hash-id` | 1 | No SHA-1 helper on this machine. Stop and report that `sha1sum` or `shasum` has to be installed. Never hand-compute the hash | +| `jsc-gitea:wiki` write | failure | Retry once. Still failing, stop and report the page name that was not written, and print the report body so the work is not lost. Never report a page as written when it was not | -Done when the page URL is reported, or the skipped write is reported with its reason. +Write the content page before its row in `REPORT_CONTENTS`, never the two at once: a directory row pointing at a page whose write failed is worse than a missing row. -## 5. Close +Done when the page URL is reported, or the skipped write is reported with its reason, or the run stopped on a read that returned 7 or 8 and that status was reported. -State the period label, entry count, repositories covered, template source, and the page URL. Name every unfinished work package that carried over — that list is what the next period starts from. +## 4. Close -Done when those five facts and the carry-over list are stated. +State the period label, entry count, repositories covered, template source, and the page URL. Name every unfinished work package that carried over — that list is what the next period starts from. State `ELAPSED_MISSING` and `TOKEN_MISSING` whenever either is above 0, so a small total is read as missing data rather than a light week. + +Done when those five facts, the carry-over list and the two missing-data counts are stated. diff --git a/skills/stats/SKILL.md b/skills/stats/SKILL.md index 9a1f396..033072a 100644 --- a/skills/stats/SKILL.md +++ b/skills/stats/SKILL.md @@ -17,5 +17,11 @@ Data is recorded continuously by `jsc-hooks/hooks/skill-usage.sh` under `$JSC_HO ## Reporting -1. Run the tool directly and present the output as a table. Done when every line the tool printed appears as one table row. -2. When the tool prints no rows, explain that `jsc-hooks` must be installed and wired first via `jsc-hooks:hooks-install`. Done when that instruction is reported and no table is shown. +1. Run the tool directly and branch on its exit code — never read the printed lines without it. + + | Exit | Do | + | --- | --- | + | 0 | Present every printed line as one table row. No line printed is still exit 0: the data file under `$JSC_HOME/usage/` does not exist yet, which is a count of zero, not a failure — say the counts are zero and that `jsc-hooks` has to be installed and wired via `jsc-hooks:hooks-install` before anything is recorded, and show no table | + | 2 | The subcommand or the `--cli` argument was rejected — the subcommand is neither `skills` nor `chains`, or `--cli` came with no value. Fix the argument and rerun. Never rerun the same command unchanged, and never report the counts as zero: nothing was read | + + Done when the exit code was read and the branch it names was taken. diff --git a/skills/worklog/SKILL.md b/skills/worklog/SKILL.md index 5498816..05cc50d 100644 --- a/skills/worklog/SKILL.md +++ b/skills/worklog/SKILL.md @@ -21,13 +21,15 @@ Guardrail: append entries at the end of the page and leave the existing ones unt ## Items to collect +Rows 3, 4, 5 and 6 each hit a different source and none of them reads another's output, so **fetch the four in parallel**. Rows 1 and 2 are two local `git` reads that join the same batch. + | # | Item | Source | | --- | --- | --- | | 1 | Repository name | Parse `{owner}/{repo}` from `git remote get-url origin`. This is the code repo — never pass it to `wiki-url`, which takes the wiki-hosting repo | | 2 | Branch name | `git branch --show-current` | -| 3 | Plan name | Absolute link to the plan page: `[PLAN_{HASH}]()`. Resolve the hosting repo with `jsc-gitea/tools/gitea.sh wiki-repo PLAN`, then take `` from `gitea.sh wiki-url PLAN_{HASH}` — PLAN and LOG may live in different wiki repos, and `[[...]]` only resolves inside one wiki. `wiki-repo` exit 3 (no wiki repo configured for that type) or `wiki-url` exit 4 (page not found) → fill the literal 「無」 for this row and carry on; a `worklog` run triggered from `maintain` normally has no plan page | -| 4 | Work package id | Absolute link to the work package heading: `[WP-xx](#wp-xx)`. Resolve the hosting repo with `gitea.sh wiki-repo ANALYZE`, then take `` from `gitea.sh wiki-url ANALYZE_{HASH}`. A comment-fix round links to the same work package it belongs to. Same fallback as row 3: `wiki-repo` exit 3 or `wiki-url` exit 4 → fill 「無」 and carry on | -| 5 | Elapsed time | `jsc-hooks/hooks/session-timer.sh report {session_id}` (seconds; convert to h/m). Count only this task, so read it at the moment the task ends | +| 3 | Plan name | Absolute link to the plan page: `[PLAN_{HASH}]()`. Resolve the hosting repo with `jsc-gitea/tools/gitea.sh wiki-repo PLAN`, then take `` from `gitea.sh wiki-url PLAN_{HASH}` — PLAN and LOG may live in different wiki repos, and `[[...]]` only resolves inside one wiki. `wiki-repo` exit 3 (no wiki repo configured for that type) or `wiki-url` exit 4 (page not found) → fill the literal 「無」 for this row and carry on; a `worklog` run triggered from `maintain` normally has no plan page. `wiki-url` exit 5 → stop and report that the page carries no `html_url`; never assemble the URL by hand. `wiki-url` exit 7 (token invalid or no permission, HTTP 401/403) or exit 8 (other API failure) → stop and report the token or API status; never fill 「無」, because that records a page that exists as a page that does not | +| 4 | Work package id | Absolute link to the work package heading: `[WP-xx](#wp-xx)`. Resolve the hosting repo with `gitea.sh wiki-repo ANALYZE`, then take `` from `gitea.sh wiki-url ANALYZE_{HASH}`. A comment-fix round links to the same work package it belongs to. Same branching as row 3: `wiki-repo` exit 3 or `wiki-url` exit 4 → fill 「無」 and carry on; `wiki-url` exit 5 → stop and report that the page carries no `html_url`, never assemble the URL by hand; `wiki-url` exit 7 or 8 → stop and report the token or API status, never fill 「無」 | +| 5 | Elapsed time | `jsc-hooks/hooks/session-timer.sh report {session_id}` prints `{sid} {seconds}` and always exits 0. Convert the seconds to h/m and read it at the moment the task ends, so it counts only this task. `0` means the timer holds no start record for that session, not a task that took no time: fill the literal 「無資料」 and say the timer had no record. Never estimate the duration from the transcript | | 6 | Token usage | `tools/token-usage.sh {session_id}` per CLI that ran; it prints `inputoutput`. Pass the same `{session_id}` as row 5 so the elapsed time and the token count describe one task. Fill `N/A` in both columns when it prints `N/A`; exit 2 means the CLI name is not one of claude / codex / copilot / antigravity / kiro, so fix the name and rerun | | 7 | Task status | One of the literal values 「完成」, 「部分完成」, 「阻塞」 (with reason when blocked). Derive it from the session when the session shows it; otherwise ask via `jsc-ask:ask`, offering those three literals as the options and stating each option's impact scope (「完成」 closes the task, 「部分完成」 leaves the remainder open for the next run, 「阻塞」 records the blocker and hands it back to the operator) | | 8 | Details and outputs | One line per changed file or produced page: what changed there and why. A round that changed nothing says what was tried and why it was dropped | @@ -38,12 +40,39 @@ The `{HASH}` in every page name above is computed with `jsc-gitea/tools/hash-id` ## Write the entry -1. Resolve the wiki repo hosting LOG pages with `gitea.sh wiki-repo LOG`: it reads `JSC_WIKI_REPO_LOG` first and falls back to `JSC_WIKI_REPO` only when that one is unset. Inspect the inherited shell environment first, and ask the user per the `jsc-ask:ask` rules when neither resolves. Keep to the LOG variable — another page type's `JSC_WIKI_REPO_{TYPE}` never stands in for it. Done when the hosting `{owner}/{repo}` is known. -2. Compute `{HASH}` from the code repo's `{owner}/{repo}` with `jsc-gitea/tools/hash-id`. Done when the 8-character `{HASH}` is known. -3. Run `tools/worklog-target.sh "{HASH}" all`. Use `PAGE` for `LOG_{HASH}` and `CONTENTS` for `LOG_CONTENTS`. Done when both page names are known. -4. Fix the work week: the Friday of the current work week drives the page content and the row dates. Done when that Friday is fixed as a `yyyy-MM-dd` date. -5. Fill `templates/log-entry.md` with the ten facts of this one task and save it to a file. Done when that file holds exactly one entry. -6. Run `tools/worklog-pending.sh merge {HASH} {entry file}`. It prints `MERGED=` (pending content in time order, then this task's entry), `CLAIM=` (the pending files it took) and `PENDING=` (how many). Pending content was written by an earlier stage that ended without a work log, so it belongs in **this** write. Done when `MERGED` and `CLAIM` are known. -7. Read `PAGE` via `jsc-gitea:wiki`. Create it from the structure in `templates/log-entry.md` when it does not exist, then append the whole `MERGED` content at the end. Done when every entry in `MERGED` exists on `PAGE`. -8. Update `CONTENTS` in the same pass (apply `templates/log-contents.md`; add the row if missing, otherwise refresh its 條目數 and 最後更新). Done when the row for `PAGE` carries this week's Friday date. -9. Close the pending area on the result of steps 7 and 8: `tools/worklog-pending.sh commit {HASH} {CLAIM}` after both succeeded, or `tools/worklog-pending.sh abort {HASH} {CLAIM}` after either failed. `abort` keeps every pending file for the retry, so keep the entry file too and rerun from step 6. Done when one of the two ran and printed its count. +1. Resolve the LOG wiki repo and the entry's `{HASH}` **in parallel** — neither needs the other. + - Repo: `gitea.sh wiki-repo LOG`. Exit 3 means no LOG wiki repo is configured; hand that to `jsc-gitea:wiki`, which owns the resolution order and the question to ask. Exit 2 means the type argument was misspelled, so fix it and rerun. + - Hash: `jsc-gitea/tools/hash-id "{owner}/{repo}"` on the code repo from row 1. Exit 1 means this machine has no SHA-1 helper: stop and report that `sha1sum` or `shasum` has to be installed, and never hand-compute the hash. + + Done when the hosting `{owner}/{repo}` and the 8-character `{HASH}` are both known. +2. Run `tools/worklog-target.sh "{HASH}" all` and `tools/worklog-target.sh friday` in parallel. `all` prints `PAGE=LOG_{HASH}` and `CONTENTS=LOG_CONTENTS`; `friday` prints the Friday of the current work week as `yyyy-MM-dd`, which drives the page content and the row dates. Exit 2 means the arguments were rejected, so fix them and rerun. Exit 4 from `friday` means this machine's `date` does no date arithmetic: stop and report it, because a hand-picked Friday is exactly what goes wrong across a month or year boundary. Done when both page names and that Friday date are known. +3. Fill `templates/log-entry.md` with the ten facts of this one task and save it to a file. Done when that file holds exactly one entry. +4. Run `tools/worklog-pending.sh merge {HASH} {entry file}`. It prints `MERGED=` (pending content in time order, then this task's entry), `CLAIM=` (the pending files it took) and `PENDING=` (how many). Pending content was written by an earlier stage that ended without a work log, so it belongs in **this** write. + + | Exit | Do | + | --- | --- | + | 0 | Carry on with `MERGED` and `CLAIM`. `PENDING=0` is normal and still exit 0 | + | 1 | A read or write under `$JSC_HOME/worklog-pending` failed. Stop and report the path from the message; nothing was deleted, so a rerun loses nothing | + | 2 | The `{HASH}` is not 8 uppercase alphanumerics, or the entry file argument is missing. Fix the argument and rerun from step 1 | + + Done when `MERGED` and `CLAIM` are known. +5. Read `PAGE` via `jsc-gitea:wiki`, and branch on the exit code the underlying `gitea.sh wiki-get` returned. **Only exit 4 means the page is not there yet.** Reading any other code as "it does not exist" builds a fresh page from `templates/log-entry.md` and appends to that — which replaces the whole existing work log with this one entry, and no entry on it can be recovered from the wiki afterwards. + + | Exit | Do | + | --- | --- | + | 0 | The existing content is in hand. Append the whole `MERGED` content at the end of it, and leave every existing entry byte for byte as it was. Never rebuild the page from the template on this code | + | 4 | The page really is absent. Create it from the structure in `templates/log-entry.md`, then append `MERGED` | + | 7 | The key is invalid or lacks permission, so the old content is unknown. Stop and report the key problem, write nothing and create no page — a page created here would take the place of a log that is still on the server | + | 8 | Some other API failure. Stop and report that status, write nothing and create no page. Retry only after the API is back | + + A failed write stops the run and goes to step 7 as a failure — never report the page as written when it was not. Done when every entry in `MERGED` is on `PAGE`, every entry that was already there is still there, and any 4 / 7 / 8 branch was followed as stated. +6. Update `CONTENTS` in the same pass (apply `templates/log-contents.md`; add the row if missing, otherwise refresh its 條目數 and 最後更新). Its read branches exactly as step 5 does: only exit 4 creates the directory page from the template, while 7 and 8 stop the run rather than rebuild a directory whose other rows were never read. Touch no row that belongs to another page. Done when the row for `PAGE` carries this week's Friday date from step 2 and every other row is unchanged. +7. Close the pending area on the result of steps 5 and 6: `tools/worklog-pending.sh commit {HASH} {CLAIM}` after both succeeded, or `tools/worklog-pending.sh abort {HASH} {CLAIM}` after either failed. `abort` keeps every pending file for the retry, so keep the entry file too and rerun from step 4. + + | Exit | Do | + | --- | --- | + | 0 | Report the printed count. This is the only ending that clears pending content | + | 1 | A pending file or the claim list could not be removed. Report the path and say the pending area still holds content, so the next run will merge it again — duplicate entries on `PAGE` are the thing to watch for | + | 2 | The claim path is not the one `merge` produced, or it points outside this `{HASH}`'s pending directory. Rerun from step 4 with the `CLAIM` value that `merge` printed; never pass a hand-written path | + + Done when one of the two ran and its exit code was reported. diff --git a/templates/learn-contents.md b/templates/learn-contents.md index 3b84991..77be150 100644 --- a/templates/learn-contents.md +++ b/templates/learn-contents.md @@ -1,6 +1,8 @@ # 教訓目錄 > 由 `jsc-log:learn` 維護。這是教訓目錄頁 `LEARN_CONTENTS`。每個存取庫一列;`LEARN_{HASH}` 的 `{HASH}` 依共用 wiki hash 規則產生:先取 `{owner}/{repo}` 的 SHA-1 前 8 碼並轉成大寫;首碼若是 `0-9`、`A`、`B`、`C`,改用 `H` 加上原前 7 碼,總長維持 8 碼。 +> 寫入語意:一列代表一個存取庫。先讀整頁,找得到該存取庫既有的那一列就更新那一列,找不到才新增一列。 +> 禁止整頁覆蓋,也不得改動別人的列。 | 存取庫名稱 | 教訓紀錄 | 最後更新時間 | | --- | --- | --- | diff --git a/templates/log-contents.md b/templates/log-contents.md index 7fd5cbd..f80f40f 100644 --- a/templates/log-contents.md +++ b/templates/log-contents.md @@ -1,6 +1,8 @@ # 日誌目錄 > 由 `jsc-log:worklog` 維護。每個日誌頁一列;頁名使用共享 `HASH` 規則,頁內仍依該週五日期整理。 +> 寫入語意:一列代表一個日誌頁。先讀整頁,找得到該頁既有的那一列就更新那一列,找不到才新增一列。 +> 禁止整頁覆蓋,也不得改動別人的列。 | 日誌頁 | 週五日期 | 條目數 | 最後更新 | | --- | --- | --- | --- | diff --git a/templates/report-contents.md b/templates/report-contents.md index 1322a73..12ee8a4 100644 --- a/templates/report-contents.md +++ b/templates/report-contents.md @@ -1,6 +1,8 @@ # 報表目錄 > 由 `jsc-log:report` 維護。年、月、週、日各一頁;`HASH` 取 `{owner}/{repo}/{期間}`,算法與其他頁面共用。 +> 寫入語意:一列代表一個報表頁,也就是一個存取庫的一種期間。先讀整頁,找得到該報表頁既有的那一列就更新那一列,找不到才新增一列。 +> 禁止整頁覆蓋,也不得改動別人的列。 | 報表頁 | 期間 | 最新一期 | 期數 | 最後更新 | | --- | --- | --- | --- | --- | diff --git a/tools/usage-stats.sh b/tools/usage-stats.sh index b89d029..1360fda 100755 --- a/tools/usage-stats.sh +++ b/tools/usage-stats.sh @@ -4,13 +4,23 @@ # usage-stats.sh skills [--cli ] # 每個 skill 的使用次數(降冪) # usage-stats.sh chains [--cli ] # 每條 from -> to 呼叫鏈的次數(降冪) # 資料: $JSC_HOME/usage/skills.jsonl、chains.jsonl(預設 $HOME/.jsc) +# +# 結束碼: 0=成功(資料檔不存在也算成功,印不出任何一行)2=用法錯誤(沒給子命令、 +# 子命令不認得、--cli 沒帶值) +# 資料檔不存在為什麼算成功: hook 還沒記過任何一次用量就是這個狀態,那是「零次」,不是故障。 set -u JSC_HOME="${JSC_HOME:-$HOME/.jsc}" -cmd="${1:?usage: usage-stats.sh skills|chains [--cli ]}"; shift +usage() { + echo '用法:usage-stats.sh skills|chains [--cli ]' >&2 + exit 2 +} + +cmd="${1:-}"; [ -n "$cmd" ] || usage +shift cli="" while [ $# -gt 0 ]; do case "$1" in - --cli) cli="${2:?--cli needs a value}"; shift 2 ;; + --cli) cli="${2:-}"; [ -n "$cli" ] || usage; shift 2 ;; *) shift ;; esac done @@ -28,5 +38,5 @@ case "$cmd" in f="$JSC_HOME/usage/chains.jsonl"; [ -f "$f" ] || exit 0 by_cli < "$f" | sed -n 's/.*"from":"\([^"]*\)".*"to":"\([^"]*\)".*/\1 -> \2/p' | rank ;; *) - echo "unknown command: $cmd" >&2; exit 2 ;; + echo "不認得的子命令:$cmd" >&2; usage ;; esac -- 2.53.0 From 1bec87b04723f29068c7b543c98fb4c538afbb46 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 31 Aug 2026 11:07:16 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(tools):=20=E5=A0=B1=E8=A1=A8=E5=BD=99?= =?UTF-8?q?=E7=B8=BD=E8=88=87=E5=B7=A5=E4=BD=9C=E9=80=B1=E9=80=B1=E4=BA=94?= =?UTF-8?q?=E9=83=BD=E4=BA=A4=E7=B5=A6=E8=85=B3=E6=9C=AC=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - What:新增 tools/log-aggregate.sh,把日誌頁彙總成報表數字; tools/worklog-target.sh 新增 friday 子命令,印出該工作週的週五。 - Why:彙總與日期原本只寫在技能散文裡,每次跑都靠模型自己數。 四個條目只有一個填了花費時間時,模型容易把它攤到每一筆,得出一個沒人做過的工時。 週五同理,跨月、跨年那一週用手算就會差一天。規則寫進程式,每次跑才會一樣。 - How:log-aggregate.sh 讀日誌條目的固定版型,依條目標題的日期取範圍內的條目, 印出條目數、存取庫、花費時間、各 CLI 的 token 用量與狀態計數。 「無資料不估算」寫死在程式裡:沒填時間的條目不進總和,只進 ELAPSED_MISSING; 整段期間都沒有時間就印「無資料」,不印 0,因為 0 會被讀成「花了 0 分鐘」。 friday 的週界向 report-range.sh weekly 取得,這裡只做「週一加四天」,不自己定義週的起點。 - Who:report 技能的數字彙總,worklog 技能的日誌頁週次。 --- tools/log-aggregate.sh | 150 ++++++++++++++++++++++++++++++++++++++++ tools/worklog-target.sh | 45 +++++++++++- 2 files changed, 193 insertions(+), 2 deletions(-) create mode 100755 tools/log-aggregate.sh diff --git a/tools/log-aggregate.sh b/tools/log-aggregate.sh new file mode 100755 index 0000000..493fc07 --- /dev/null +++ b/tools/log-aggregate.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env sh +# log-aggregate.sh — 把工作日誌頁的條目彙總成報表數字(供 jsc-log:report 呼叫)。 +# +# 用法: +# log-aggregate.sh <起 yyyy-MM-dd> <訖 yyyy-MM-dd> [日誌頁檔案 ...] +# 檔案省略時讀標準輸入。可以一次傳多份 LOG_{HASH} 的內容,數字會跨頁加總。 +# +# 輸入格式即 templates/log-entry.md 的固定版型: +# 條目標題「## {yyyy-MM-dd HH:mm} {摘要}」,底下是「| 欄位 | 內容 |」表格。 +# 只認四個欄位:存取庫名稱、花費時間、token 用量、任務狀態。 +# 起訖含頭含尾,比對的是條目標題那個日期。 +# +# 輸出(一行一項,KEY=值;值有多欄時以 TAB 分隔): +# ENTRIES=<期間內條目數> +# REPOS=<相異存取庫數> +# REPO= 每個存取庫一行,依字典序 +# ELAPSED_MINUTES=<分鐘數|無資料> 有填花費時間的條目才加總 +# ELAPSED_ENTRIES=<有填花費時間的條目數> +# ELAPSED_MISSING=<沒填花費時間的條目數> +# TOKEN= 每個 CLI 一行,依 CLI 名字典序 +# TOKEN_MISSING= +# STATUS=<狀態><條目數> 每個狀態一行,依字典序 +# +# 無資料不估算(本腳本的硬規則,不是散文建議): +# 花費時間欄空白、填「無」或「無資料」的條目,不進 ELAPSED_MINUTES,只進 ELAPSED_MISSING。 +# 四個條目只有一個有時間時,總和就是那一個的時間,不乘四也不取平均。 +# token 欄填 N/A 的條目同理,只進 TOKEN_MISSING。 +# 全部條目都沒有時間時,ELAPSED_MINUTES 印「無資料」,不印 0——0 會被讀成「花了 0 分鐘」。 +# +# 結束碼: 0=成功 2=用法錯誤 3=期間內沒有條目(仍印出全零結果)4=讀不到輸入檔 +set -u + +usage() { + echo '用法:log-aggregate.sh <起 yyyy-MM-dd> <訖 yyyy-MM-dd> [日誌頁檔案 ...]' >&2 + exit 2 +} + +start="${1:-}"; [ -n "$start" ] || usage +shift +end="${1:-}"; [ -n "$end" ] || usage +shift + +for d in "$start" "$end"; do + case "$d" in + [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; + *) echo "日期格式要是 yyyy-MM-dd:$d" >&2; exit 2 ;; + esac +done +if [ "$(printf '%s\n%s\n' "$start" "$end" | sort | head -n1)" != "$start" ]; then + echo "起始日期比結束日期晚:$start 到 $end" >&2 + exit 2 +fi + +for f in "$@"; do + [ -r "$f" ] || { echo "讀不到日誌頁檔案:$f" >&2; exit 4; } +done + +aggregate() { + awk -v start="$start" -v end="$end" ' + function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s } + function sortkeys(arr, out, i, j, n, t, k) { + n = 0 + for (k in arr) { n++; out[n] = k } + for (i = 1; i < n; i++) + for (j = i + 1; j <= n; j++) + if (out[j] < out[i]) { t = out[i]; out[i] = out[j]; out[j] = t } + return n + } + # 花費時間欄轉分鐘。認得「{h} 小時 {m} 分」、「{h} 小時」、「{m} 分」三種寫法。 + # 一個數字都抓不到就回 -1,交給呼叫端算成「無資料」,不猜。 + function minutes(v, h, m, tmp) { + h = -1; m = -1 + tmp = v + if (match(tmp, /[0-9]+[ ]*小時/)) { h = substr(tmp, RSTART, RLENGTH) + 0 } + if (match(tmp, /[0-9]+[ ]*分/)) { m = substr(tmp, RSTART, RLENGTH) + 0 } + if (h < 0 && m < 0) return -1 + if (h < 0) h = 0 + if (m < 0) m = 0 + return h * 60 + m + } + # 狀態只認三個字面值,其餘一律歸「其他」。先比對「部分完成」,它包含「完成」。 + function status_of(v) { + if (index(v, "部分完成") > 0) return "部分完成" + if (index(v, "阻塞") > 0) return "阻塞" + if (index(v, "完成") > 0) return "完成" + return "其他" + } + BEGIN { + FS = "|" + entries = 0; repos_n = 0 + elapsed_total = 0; elapsed_entries = 0; elapsed_missing = 0 + token_missing = 0 + in_range = 0 + } + /^##[ \t]/ { + in_range = 0 + if (match($0, /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/)) { + d = substr($0, RSTART, RLENGTH) + if (d >= start && d <= end) { in_range = 1; entries++ } + } + next + } + in_range == 1 && /^[ \t]*\|/ { + key = trim($2); val = trim($3) + if (key == "存取庫名稱") { + if (val != "" && val !~ /^\{/) { if (!(val in repos)) { repos[val] = 1; repos_n++ } } + } else if (key == "花費時間") { + mm = minutes(val) + if (mm < 0) { elapsed_missing++ } else { elapsed_total += mm; elapsed_entries++ } + } else if (key == "token 用量" || key == "Token 用量") { + rest = val; got = 0 + while (match(rest, /[A-Za-z][A-Za-z0-9_-]*[ ]*:[ ]*[0-9]+[ ]*\/[ ]*[0-9]+/)) { + pair = substr(rest, RSTART, RLENGTH) + rest = substr(rest, RSTART + RLENGTH) + split(pair, a, ":") + cli = trim(a[1]) + split(a[2], b, "/") + tin[cli] += trim(b[1]) + 0 + tout[cli] += trim(b[2]) + 0 + got = 1 + } + if (got == 0) token_missing++ + } else if (key == "任務狀態") { + if (val != "" && val !~ /^\{/) { st[status_of(val)]++ } + } + } + END { + printf "ENTRIES=%d\n", entries + printf "REPOS=%d\n", repos_n + n = sortkeys(repos, sorted) + for (i = 1; i <= n; i++) printf "REPO=%s\n", sorted[i] + if (elapsed_entries > 0) printf "ELAPSED_MINUTES=%d\n", elapsed_total + else printf "ELAPSED_MINUTES=%s\n", "無資料" + printf "ELAPSED_ENTRIES=%d\n", elapsed_entries + printf "ELAPSED_MISSING=%d\n", elapsed_missing + n = sortkeys(tin, sorted) + for (i = 1; i <= n; i++) printf "TOKEN=%s\t%d\t%d\n", sorted[i], tin[sorted[i]], tout[sorted[i]] + printf "TOKEN_MISSING=%d\n", token_missing + n = sortkeys(st, sorted) + for (i = 1; i <= n; i++) printf "STATUS=%s\t%d\n", sorted[i], st[sorted[i]] + exit (entries > 0 ? 0 : 3) + } + ' "$@" +} + +if [ "$#" -gt 0 ]; then + aggregate "$@" +else + aggregate - +fi diff --git a/tools/worklog-target.sh b/tools/worklog-target.sh index f8b102f..f714fbb 100755 --- a/tools/worklog-target.sh +++ b/tools/worklog-target.sh @@ -1,18 +1,59 @@ #!/usr/bin/env sh -# worklog-target.sh: 由已算好的 hash 組出 worklog wiki 目標頁名稱。 +# worklog-target.sh: 由已算好的 hash 組出 worklog wiki 目標頁名稱,並算出工作週的週五。 # hash 一律由 jsc-gitea/tools/hash-id 算出後傳入,本腳本不再自行計算 SHA-1。 # 用法: # worklog-target.sh [page|contents|all] +# worklog-target.sh friday [基準日期 yyyy-MM-dd] +# +# friday 印出一行 yyyy-MM-dd:基準日期所在工作週的週五(基準日期省略時用今天)。 +# 週界不在這裡算,一律問 report-range.sh weekly——週一起算的 ISO 週定義只有那一支說了算, +# 兩邊各算一次就會在跨月、跨年的那一週各走各的。這裡只做「週一加四天」這一步。 +# +# 結束碼: 0=成功 2=用法錯誤 4=系統的 date 不支援日期運算(由 report-range.sh 判定) set -eu +HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + usage() { - echo 'usage: worklog-target.sh [page|contents|all]' >&2 + cat >&2 <<'EOF' +用法: + worklog-target.sh [page|contents|all] 組出 LOG_{HASH} 與 LOG_CONTENTS + worklog-target.sh friday [yyyy-MM-dd] 印出該工作週的週五日期 +結束碼: 0=成功 2=用法錯誤 4=系統的 date 不支援日期運算 +EOF exit 2 } +add_days() { # $1=yyyy-MM-dd $2=要加的天數 + if date -d "$1 +$2 days" +%Y-%m-%d >/dev/null 2>&1; then + date -d "$1 +$2 days" +%Y-%m-%d + return 0 + fi + if date -j -f %Y-%m-%d -v"+$2d" "$1" +%Y-%m-%d >/dev/null 2>&1; then + date -j -f %Y-%m-%d -v"+$2d" "$1" +%Y-%m-%d + return 0 + fi + echo "系統的 date 不支援日期運算,算不出工作週的週五" >&2 + exit 4 +} + [ "$#" -ge 1 ] || usage [ "$#" -le 2 ] || usage +if [ "$1" = friday ]; then + base="${2:-}" + if [ -n "$base" ]; then + case "$base" in + [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; + *) echo "基準日期格式要是 yyyy-MM-dd:$base" >&2; exit 2 ;; + esac + fi + range=$(sh "$HERE/report-range.sh" weekly ${base:+"$base"}) || exit $? + monday=$(printf '%s\n' "$range" | cut -f1) + add_days "$monday" 4 + exit 0 +fi + hash=$1 field=${2:-page} -- 2.53.0 From 04f28d4505506c9cf19f9383085b465d1bb8e5e6 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 31 Aug 2026 11:07:16 +0800 Subject: [PATCH 3/4] =?UTF-8?q?docs(log):=20=E8=A3=9C=E4=B8=8A=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=B5=90=E6=9D=9F=E7=A2=BC=E5=AE=A3=E5=91=8A=E8=88=87?= =?UTF-8?q?=E6=96=B0=E6=B5=81=E7=A8=8B=E8=AA=AA=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - What:README 的工具表補進彙總腳本,並改寫 worklog 與 report 兩節; tools/token-usage.sh 的檔頭改寫成完整的結束碼宣告。 - Why:README 還停在舊流程,讀的人會以為報表數字仍由技能自己數,也不知道週五已經有腳本可用。 token-usage.sh 原本只寫「護欄回傳 2」,沒說來源讀不到時印 N/A 也算成功, 接手的人容易把那個情況當成故障,白追一輪。 - How:工具表加一列,寫出彙總腳本的輸出欄位與「無資料不估算」; worklog 一節寫出四項來源併行取得、週五由腳本算; report 一節寫出三線併行、彙總改走腳本,以及教訓頁另解自己的 wiki 存取庫。 相依清單補上 jsc-ask 一列,說明它負責問期間與任務狀態。 - Who:整個 log 技能組的說明文件。 --- README.md | 8 +++++--- tools/token-usage.sh | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index df93de0..2ee3e00 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,11 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 | 工具 | 用途 | | --- | --- | | `tools/usage-stats.sh` | 聚合 `$JSC_HOME/usage/*.jsonl`:`skills` 列技能使用次數、`chains` 列呼叫鏈次數(皆降冪),`--cli ` 過濾 | -| `tools/worklog-target.sh` | 接收已由 `jsc-gitea/tools/hash-id` 算好的 `HASH`,組出 `LOG_{HASH}`、`LOG_CONTENTS`(本身不再計算 SHA-1) | +| `tools/worklog-target.sh` | 接收已由 `jsc-gitea/tools/hash-id` 算好的 `HASH`,組出 `LOG_{HASH}`、`LOG_CONTENTS`(本身不再計算 SHA-1)。`friday [yyyy-MM-dd]` 印出該工作週的週五,週界向 `report-range.sh weekly` 取得,這裡只加四天——跨月、跨年那一週交給人算就會差一天 | | `tools/worklog-pending.sh` | 待寫入日誌的暫存區,存放於 `$JSC_HOME/worklog-pending/{HASH}/`。`add {HASH} {檔案}` 存一段內容(`jsc-sdlc` 的階段回報發現沒寫日誌時會呼叫),`cat {HASH}` 依時間印出全部、`list` 列路徑、`clear` 清掉全部。寫日誌走三段式:`merge {HASH} {本次條目檔}` 合成「暫存內容在前、本次條目在後」並印出 `MERGED=`、`CLAIM=`、`PENDING=`;wiki 寫入成功後 `commit {HASH} {CLAIM}` 只清掉併入清單上那幾個檔;寫入失敗就 `abort {HASH} {CLAIM}`,暫存一個都不刪。結束碼 `3` 代表沒有暫存內容(`merge` 沒暫存仍是 `0`)。**清除只發生在寫進 wiki 成功之後**,先清再寫會兩邊都沒有 | | `tools/report-range.sh` | 算報表期間:`report-range.sh {daily\|weekly\|monthly\|yearly} [yyyy-MM-dd]` 印出「起訖標籤期間」,含頭含尾。週採 ISO-8601(週一起算),標籤如 `2026-W35`。日期運算交給系統的 `date`,不自己算閏年 | | `tools/report-template.sh` | 解析報表範本位置:`resolve {period}` 印出「路徑project\|skill」,`list` 一次列四種期間。工作目錄的 `.jsc/templates/report-{period}.md` 優先於技能自帶的 `templates/` | +| `tools/log-aggregate.sh` | 把日誌頁彙總成報表數字:`log-aggregate.sh {起} {訖} [日誌頁檔案 ...]`(省略檔案就讀標準輸入),印出 `ENTRIES=`、`REPOS=`、`REPO=`、`ELAPSED_MINUTES=`、`ELAPSED_ENTRIES=`、`ELAPSED_MISSING=`、`TOKEN=`、`TOKEN_MISSING=`、`STATUS=`。「無資料不估算」寫在腳本裡:沒填花費時間的條目不進總和,只進 `ELAPSED_MISSING`;整段期間都沒有時間就印「無資料」,不印 `0`。結束碼 `3` 代表期間內沒有條目,全零結果照樣印出來 | | `tools/token-usage.sh` | 讀單一 CLI 這次工作的 token 用量,印出「input(tab)output」;來源讀不到就印「N/A(tab)N/A」並正常結束。第二個參數傳 session id,就只讀該階段的 transcript,數字才會跟花費時間對得上。各 CLI 的取得方式寫在腳本開頭註解 | ## Skills 目錄 @@ -39,7 +40,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 每完成一個任務就寫一筆日誌。任務有三種:一個工作包、一輪 PR 留言修正、一個獨立的修正提交。下一個任務開始前先把這一筆寫完,同一個工作包跑五輪留言修正就是五筆,各自帶自己的花費時間與 token 用量,附加到同一頁 `LOG_{HASH}`——連「試了卻沒改到檔案」的那一輪也留下來,那段時間才看得見。 -每筆蒐集十項資訊(存取庫、分支、計畫連結、工作包連結、花費時間、token 用量、任務狀態、執行細節、困難與解決、PR 目標分支),用 `tools/worklog-target.sh` 產生目標頁,套範本後附加到 `LOG_{HASH}` 與 `LOG_CONTENTS`。寫入前跑 `tools/worklog-pending.sh merge {HASH} {本次條目檔}`:之前有階段跑完沒寫日誌,內容暫存在那裡,這次一併寫進去;wiki 寫入成功才 `commit` 清掉暫存,失敗就 `abort` 保留。 +每筆蒐集十項資訊(存取庫、分支、計畫連結、工作包連結、花費時間、token 用量、任務狀態、執行細節、困難與解決、PR 目標分支)。計畫連結、工作包連結、花費時間、token 用量四項來源互不相依,併行取得;存取庫解析與 `HASH` 計算也併行。用 `tools/worklog-target.sh` 產生目標頁,工作週的週五由同一支的 `friday` 子命令算出,套範本後附加到 `LOG_{HASH}` 與 `LOG_CONTENTS`。寫入前跑 `tools/worklog-pending.sh merge {HASH} {本次條目檔}`:之前有階段跑完沒寫日誌,內容暫存在那裡,這次一併寫進去;wiki 寫入成功才 `commit` 清掉暫存,失敗就 `abort` 保留。 ### `stats` @@ -51,7 +52,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安 ### `report` -把工作日誌總結成年報、月報、週報或日報。期間由 `tools/report-range.sh` 算出(週次採 ISO-8601),範本由 `tools/report-template.sh` 解析——工作目錄的 `.jsc/templates/report-{period}.md` 優先,沒有才用技能自帶的那份。讀 `LOG_CONTENTS` 列出的所有日誌頁,取工作日期落在期間內的條目,統計條目數、涵蓋存取庫、花費時間、token 用量、困難與未結項目,填進範本後寫入 wiki `REPORT_{HASH}`(`HASH` 取 `{owner}/{repo}/{期間}`),同一期間重跑只換掉那一節。單筆工作紀錄請用 `worklog`。 +把工作日誌總結成年報、月報、週報或日報。期間由 `tools/report-range.sh` 算出(週次採 ISO-8601),範本由 `tools/report-template.sh` 解析——工作目錄的 `.jsc/templates/report-{period}.md` 優先,沒有才用技能自帶的那份。範本解析、日誌頁讀取、教訓頁讀取三線併行,各頁也一頁一個 sub agent 同時讀。讀 `LOG_CONTENTS` 列出的所有日誌頁後,交給 `tools/log-aggregate.sh` 算出條目數、涵蓋存取庫、花費時間、各 CLI token 用量與狀態計數,填進範本後寫入 wiki `REPORT_{HASH}`(`HASH` 取 `{owner}/{repo}/{期間}`,這裡的 `{owner}/{repo}` 取 REPORT wiki 存取庫,不是程式碼存取庫——本頁其他 `HASH` 取的是程式碼存取庫,只有這一處不同),同一期間重跑只換掉那一節。年報的教訓頁另解 `JSC_WIKI_REPO_LEARN`,不沿用日誌頁的存取庫。單筆工作紀錄請用 `worklog`。 @@ -79,3 +80,4 @@ Wiki 位置:日誌頁用 `JSC_WIKI_REPO_LOG`、教訓頁用 `JSC_WIKI_REPO_LEA - [`jsc-hooks`](https://gitea.jsc.idv.tw/plugins/hooks):花費時間(`session-timer.sh report`)與用量資料來源 - [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):wiki 讀寫 +- [`jsc-ask`](https://gitea.jsc.idv.tw/plugins/ask):期間、任務狀態、困難等要問使用者時的決策樹 diff --git a/tools/token-usage.sh b/tools/token-usage.sh index 5ed80b1..025ce4e 100755 --- a/tools/token-usage.sh +++ b/tools/token-usage.sh @@ -3,7 +3,9 @@ # 用法: # token-usage.sh [session-id] # cli = claude|codex|copilot|antigravity|kiro # 輸出: 一行「(tab)」。任何來源讀不到就印「N/A(tab)N/A」並 exit 0。 -# 護欄: 沒給 cli 或 cli 名稱不認得,回傳 2。 +# +# 結束碼: 0=成功(含來源讀不到而印 N/A 的情況,那是「這個 CLI 沒有可讀數字」,不是故障) +# 2=用法錯誤(沒給 cli,或 cli 名稱不在 claude|codex|copilot|antigravity|kiro 之內) # # 各 CLI 的取得方式(原本寫在 worklog 的 SKILL.md,現在收在這裡): # claude 加總 transcript JSONL(`$CLAUDE_CONFIG_DIR` 或 `~/.claude` 底下的 -- 2.53.0 From fee4185966ff3c700846f8c176bb4020a746d92f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 31 Aug 2026 11:07:16 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore(manifest):=20=E5=AE=A3=E5=91=8A=20jsc?= =?UTF-8?q?-ask=20=E8=88=87=20jsc-hooks=20=E7=9B=B8=E4=BE=9D=E4=B8=A6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E8=99=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - What:Claude、Codex 與根目錄三份 plugin 清單同步補上 jsc-ask 與 jsc-hooks 的最低版本需求, 並把版號往前推一版。 - Why:技能早就呼叫 jsc-ask 的決策樹問期間與任務狀態,也讀 jsc-hooks 的計時器與用量紀錄取數字, 清單卻只宣告 gitea。缺宣告時,部署到只裝一半的機器上不會被擋,要跑到一半才失敗, 而且失敗訊息指向 wiki 或計時器,看不出真正缺的是哪個外掛。 - How:三份清單的 requires 一起改,三份內容保持一致,免得不同 CLI 讀到不同的相依。 - Who:log 技能組的安裝與部署檢查。 --- .claude-plugin/plugin.json | 6 ++++-- .codex-plugin/plugin.json | 6 ++++-- plugin.json | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4de1ae0..4b39191 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-log", - "version": "0.1.2", + "version": "0.1.3", "description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)", "skills": "./skills", "author": { @@ -16,7 +16,9 @@ ], "jsc": { "requires": { - "jsc-gitea": ">=0.1.7" + "jsc-ask": ">=0.0.7", + "jsc-gitea": ">=0.1.7", + "jsc-hooks": ">=0.3.1" } } } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 8df7c96..40d6e43 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,11 +1,13 @@ { "name": "jsc-log", - "version": "0.1.2", + "version": "0.1.3", "description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)", "skills": "./skills", "jsc": { "requires": { - "jsc-gitea": ">=0.1.7" + "jsc-ask": ">=0.0.7", + "jsc-gitea": ">=0.1.7", + "jsc-hooks": ">=0.3.1" } } } diff --git a/plugin.json b/plugin.json index 9f1b83c..46dbb8f 100644 --- a/plugin.json +++ b/plugin.json @@ -1,11 +1,13 @@ { "name": "jsc-log", - "version": "0.1.2", + "version": "0.1.3", "description": "工作日誌(LOG_{HASH} wiki 頁)、技能使用統計與教訓紀錄(LEARN_{HASH} wiki 頁)", "skills": "./skills/", "jsc": { "requires": { - "jsc-gitea": ">=0.1.7" + "jsc-ask": ">=0.0.7", + "jsc-gitea": ">=0.1.7", + "jsc-hooks": ">=0.3.1" } } } -- 2.53.0