--- name: report description: Summarise work logs into a yearly, monthly, weekly or daily report. Resolve the period with tools/report-range.sh, resolve the template with tools/report-template.sh - a project's .jsc/templates/report-{period}.md wins over the skill's own copy - then read every log page listed in LOG_CONTENTS and keep the entries dated inside the range. Fill the template with real aggregates (entry count, repositories, elapsed time, token usage, blockers, carry-overs) and write it to wiki REPORT_{HASH}, whose full 40-character uppercase hash comes from the REPORT wiki repo's own {owner}/{repo} plus the period rather than from a code repo, appending the period as a new section. Directory pages LOG_CONTENTS, LEARN_CONTENTS and REPORT_CONTENTS all sit in the shared CONTENTS wiki repo while every content page stays in its own type's repo, so the REPORT_CONTENTS entry goes through jsc-gitea/tools/wiki-contents.sh upsert as one H2 block keyed by the page name REPORT_{HASH}, with a bullet per field and the report page linked by its absolute wiki-url. Use when someone asks for a work summary over a period; not for recording a single work package, which is jsc-log:worklog. --- # report — summarise work logs by period Reading and aggregating the log pages **MUST run as a sub agent**: it reads every page in `LOG_CONTENTS` and only a handful of entries survive the date filter. Report content is written in Traditional Chinese (STE100). ## 1. Period 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. | 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. Resolve and collect Directory pages and content pages no longer share a wiki. Every `*_CONTENTS` page — `LOG_CONTENTS`, `LEARN_CONTENTS`, `REPORT_CONTENTS` — lives in the one repo that `jsc-gitea/tools/gitea.sh wiki-repo CONTENTS` resolves (`JSC_WIKI_REPO_CONTENTS`, then `JSC_WIKI_REPO`, then exit 3; it never falls back to a page type's own variable). Each content page still lives in its own type's repo: log pages in `wiki-repo LOG`, lesson pages in `wiki-repo LEARN`, the report page in `wiki-repo REPORT`. Keep the two apart — one shared directory repo, one repo per content type — and resolve every one of them on its own. Every directory page is a list page, not a table: an H1, a `>` preamble, then one H2 block per entry whose heading is that entry's content page name, with `- {欄位名}:{值}` bullets under it. Read the links out of the bullets. Run these four lines of work in parallel — none of them consumes another's output, and the log pages are the slow one: 1. **Template.** `tools/report-template.sh resolve {period}` from the working directory prints `{path}{project|skill}`. 2. **Log pages.** `gitea.sh wiki-repo CONTENTS`, then read `LOG_CONTENTS` through `jsc-gitea:wiki`, then read **every** log page it lists, one sub agent per page. The page holds one H2 block per log page, each with a 日誌頁 bullet carrying an absolute URL, so follow each link as given rather than the H2 heading; `gitea.sh wiki-repo LOG` names the repo the log pages of this working directory sit in, and a block pointing elsewhere is another repo's log page, not a broken link. 3. **Lessons (yearly only).** Read `LEARN_CONTENTS` from the same CONTENTS repo, then read the lesson pages its blocks link for the 全年教訓 section. Resolve the lesson pages' own repo with `gitea.sh wiki-repo LEARN`, never with the LOG repo of line 2: the two directory pages now share a repo, but LOG and LEARN **content** pages routinely live in different ones, and reusing the LOG repo reads the wrong wiki. 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 CONTENTS` | 3 | Stop and report that no wiki repo is configured for the directory pages, naming `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO`. Ask per the `jsc-ask:ask` rules, then rerun. Without `LOG_CONTENTS` there is no list of log pages to read | | `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. Write through `jsc-gitea:wiki`: - Repo: the REPORT repo from step 2, line 4. It hosts the content page only; `REPORT_CONTENTS` goes to the CONTENTS repo instead. - Page: `REPORT_` plus `gitea.sh hash-id "{owner}/{repo}/{period}"`. Here `{owner}/{repo}` is **the REPORT wiki repo itself** — the value `gitea.sh wiki-repo REPORT` printed — and not the code repo the logs came from. Every other page in this skill set hashes the code repo; this one page does not, because a report spans every code repo whose logs landed in the range, so no single code repo names it. Feed `hash-id` the exact string `{REPORT wiki owner}/{REPORT wiki repo}/{period}`, with `{period}` being the literal `daily`, `weekly`, `monthly` or `yearly` — so year, month, week and day each get their own page. `hash-id` prints the full 40-character uppercase SHA-1: use it whole, never shortened and never prefixed. - 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. - Check every link before it goes on a page — the ones inside the new section and the directory block's link alike: `jsc-gitea/tools/link-check.sh {url}...`. It prints one `{OK|DEAD|SKIP}{url}{note}` line per URL and resolves Gitea URLs through the API, because a private repo answers a logged-out web request with 404 and would fail a page that is there. Only exit 0 permits the write. | Exit | Do | | --- | --- | | 0 | Every link answered. Write the section, or the directory block | | 1 | At least one link is DEAD. Write nothing and report the DEAD lines to the caller | | 2 | No URL reached the script. Pass the URLs and rerun | | 3 | The list holds a Gitea URL but `GITEA_HOST` is unset. Set it and rerun; never skip the check | | 7 | The Gitea token was rejected (HTTP 401/403). Stop and report the token problem. A rejected token makes live pages look missing, and one batch judged on that answer wipes out links that still work | - 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 block in `REPORT_CONTENTS` with `jsc-gitea/tools/wiki-contents.sh` — never hand-edit the directory page. It sits in the CONTENTS repo, not the REPORT repo, so the 報表頁 bullet links the report page as `[REPORT_{HASH}]()`, with `` from `gitea.sh wiki-url REPORT_{HASH}`. Every link on the report body and in this block takes that same `[{text}]({absolute URL})` shape; the same-wiki `[[...]]` form resolves inside one wiki only and dead-links from here without reporting an error. Build one file holding the single H2 block from `templates/report-contents.md` — the `## REPORT_{HASH}` heading, a blank line, then one `- {欄位名}:{值}` bullet per field in the template's order: the absolute link, the bare `{HASH}`, the period, the newest label, the section count and the update time. Then run: `jsc-gitea/tools/wiki-contents.sh upsert REPORT 1 "REPORT_{HASH}" {block file} templates/report-contents.md` The key is the H2 heading itself, the content page name `REPORT_{HASH}` this step already computed, and the 報表頁 bullet carries that same page as a link for a human to click. That link is exactly what must not be the key: it embeds the host and the encoded page name, so one change of `GITEA_HOST` or one difference in how Gitea encodes the page name makes this run's text differ from the last run's, the match fails, the block is appended, and the same report page now owns two blocks of which the older is never updated again. The page name depends only on the hashed `{owner}/{repo}/{period}`, so neither of those two touches it. The `1` is ``, which the script uses only while the directory page is still an old markdown table — it names the column whose cell text (the link text alone) becomes the H2 heading, and a page already in list shape ignores it. The script replaces the matching block and appends when none matches, so every block that belongs to another report page stays as it was. | 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 | | `gitea.sh hash-id` | 2 | The input was empty, which means the REPORT wiki repo or the period never reached it. Fix the string and rerun; the empty string has a valid SHA-1 and would file the report on a page nobody reads | | `gitea.sh wiki-url` | 4 / 5 | 4 means the report page write has not landed, so write it first; 5 means the page carries no `html_url`, so stop and report it and never assemble the URL by hand | | `gitea.sh wiki-url` | 7 / 8 | 7 means the token is invalid or lacks permission (HTTP 401/403), 8 means some other API failure. Both leave it unknown whether the page is there, so stop and report the token or API status. Never fold either into 4: reading an invalid key as a missing page is the same misread this table separates 7 from 4 to prevent, and here it would send the run back to rewrite a report page that is already on the server | | `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 | | `wiki-contents.sh upsert` | 0 | The block is in place. It prints `updated` or `added` plus the page it wrote | | `wiki-contents.sh upsert` | 1 | The page content could not be assembled, or the write failed. Report `REPORT_CONTENTS` as not written, together with the block content. A page with no matching block is not this code: the block is appended instead | | `wiki-contents.sh upsert` | 2 | An argument was rejected. Fix the argument and rerun this bullet; nothing was written | | `wiki-contents.sh upsert` | 3 | No CONTENTS wiki repo is configured. Report `JSC_WIKI_REPO_CONTENTS` and `JSC_WIKI_REPO` as the two variables to set. The report itself is on `REPORT_{HASH}` and stays there | | `wiki-contents.sh upsert` | 4 | The directory page is absent and the script received no template. The call above always passes one, so this code means `templates/report-contents.md` is not at that path — a partial plugin install, not a missing argument. Stop and report the path; rerunning the same command changes nothing. Reinstall the plugin, confirm the file is there, then rerun. A mistyped template path exits 2, not 4 | | `wiki-contents.sh upsert` | 7 | The token is invalid or lacks permission, so the other blocks are unknown. Stop and report the token problem; the script wrote nothing, which is what keeps those blocks alive | | `wiki-contents.sh upsert` | 8 | Some other API failure. Stop and report that status and retry only after the API is back | Write the content page before its block in `REPORT_CONTENTS`, never the two at once: a directory block pointing at a page whose write failed is worse than a missing block, and `wiki-url` cannot name a page that is not there yet. 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. ## 4. Close 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. Then record how the run ended, as the very last thing this skill does: `jsc-hooks/tools/report-status.sh skill-end jsc-log:report {status} {exit} "{detail}"` Resolve that path the way this file already resolves `jsc-gitea/tools/link-check.sh` and the other sibling plugin scripts — the sibling plugin directory, no separate lookup rule for this one call. **A missing script is not a failure here: skip this step in silence and let the run end as it stands.** The script swallows its own write errors and exits 0 even then, so nothing branches on its code either. A report that was produced stays produced whether or not the recorder was installed. | status | This skill's case | | --- | --- | | `ok` | The period's section is on `REPORT_{HASH}` and the `REPORT_CONTENTS` block carries this period. `log-aggregate.sh` exit 3 stays `ok`: an empty range is an answer, and section 2 requires the report to be produced anyway — put `ENTRIES=0` in `{detail}` so the zero is read as a counted zero, not a run that quit | | `blocked` | Nothing could be summarised and nothing was: `report-range.sh` exit 4 (this machine's `date` does no date arithmetic), `report-template.sh resolve` exit 3 (neither template exists), `wiki-repo CONTENTS` or `wiki-repo LOG` exit 3 (no directory or log pages to read), `hash-id` exit 1, or `link-check.sh` exit 3 or 7 | | `degraded` | The report body is finished and handed to the caller but did not fully land: `wiki-repo REPORT` exit 3 skipped the wiki write entirely, or the section landed and `wiki-contents.sh upsert` did not, or `wiki-repo LEARN` exit 3 left the yearly 全年教訓 section filled with 無. Say which part is missing in `{detail}` | | `failed` | The collection or the write broke part-way: `link-check.sh` exit 1 on a DEAD link, a `jsc-gitea:wiki` read or a `wiki-url` call that came back 7 or 8, `log-aggregate.sh` exit 4 on an unreadable page file, or a write that failed its retry as well | | `aborted` | The user stopped the run, most often at the period question in section 1, so no range was ever fixed and no page was read | `{exit}` is the exit code of whatever decided the status, `0` for `ok`. `{detail}` is one short line well under 200 characters: the period plus counts and exit codes, never report text, page names, branch names, or personal data. Done when the command has run, or the script was absent and this step was skipped.