現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就 中止的技能,在紀錄裡長得一模一樣。 start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾 步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在 原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。 status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜 跳過,回報失敗一律不改變技能自己的結論。
19 KiB
name, description
| name | description |
|---|---|
| worklog | Append one work-log entry to wiki LOG_{HASH} plus LOG_CONTENTS as soon as a task ends, where a task is one work package, one round of PR-comment fixes, or one standalone fix commit — one task, one entry, appended to the same page. Every entry carries the ten facts (repo, branch, plan link, work package link, elapsed time from session-timer, token usage per CLI, status, details, difficulties, PR target); HASH is the full 40-character uppercase SHA-1 of {owner}/{repo} and the work-week Friday drives the page content. LOG_{HASH} sits in the LOG wiki repo while LOG_CONTENTS sits in the separate CONTENTS repo, so the directory row goes through jsc-gitea/tools/wiki-contents.sh upsert and links the log page by its absolute wiki-url. Merge whatever tools/worklog-pending.sh holds for that HASH into the same write, then clear the pending area once that write succeeded. Trigger at the end of every such task in implement or maintain; not for planning notes. |
worklog — work log
Collection and writing MUST run as a sub agent. Entry content is written in Traditional Chinese (STE100).
What counts as one task
| Task | Ends when |
|---|---|
| A work package | its todos are done and its PR is open |
| One round of PR-comment fixes | that round's replies and pushes are done |
| A standalone fix commit | that commit is pushed |
Write the entry for a task before the next task starts — that is the whole granularity rule. Five rounds of comment fixes on one work package produce five entries on the same LOG_{HASH}, each with its own elapsed time and token count, including a round that tried something and changed no file: the time it burned is the fact worth keeping. Give each entry a heading that names the task (work package id, round number, or commit subject) so the five stay readable side by side.
Guardrail: append entries at the end of the page and leave the existing ones untouched.
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 | Link to the plan page in the shape [PLAN_{HASH}](<url>). Resolve the hosting repo with jsc-gitea/tools/gitea.sh wiki-repo PLAN, then take <url> from gitea.sh wiki-url <that repo> PLAN_{HASH} — never assemble it by hand, and never use the same-wiki [[...]] form: PLAN and LOG may live in different wiki repos, and [[...]] resolves inside one wiki only. 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 | Link to the work package heading in the shape [WP-xx](<url>#wp-xx). Resolve the hosting repo with gitea.sh wiki-repo ANALYZE, then take <url> from gitea.sh wiki-url <that repo> 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 <cli> {session_id} per CLI that ran; it prints input<TAB>output. 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 |
| 9 | Difficulties and resolutions | One pair per line. Ask via jsc-ask:ask when the session does not show them |
| 10 | PR target branch | Link to the PR page in the shape [{base branch}]({PR URL}) |
Every link in an entry is text plus an absolute URL — [{text}]({url}) — with wiki URLs coming from gitea.sh wiki-url. The same-wiki [[...]] form is out: it resolves inside one wiki only, and a link that fails that way looks like ordinary text on the page instead of reporting an error.
The {HASH} in every page name above is computed with jsc-gitea/tools/hash-id, which prints the full 40-character uppercase SHA-1 of its input — no truncation to 8 characters and no prefix rewrite. A page name shortened by hand points at a page nobody else writes to.
Write the entry
-
Resolve the LOG wiki repo and the entry's
{HASH}in parallel — neither needs the other.- Repo:
gitea.sh wiki-repo LOG. This repo hosts the content pageLOG_{HASH}only. The directory pageLOG_CONTENTSlives in a different repo and is resolved in step 6, so never reuse this value for it. Exit 3 means no LOG wiki repo is configured; hand that tojsc-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 thatsha1sumorshasumhas to be installed, and never hand-compute the hash. Exit 2 means the input was empty, which happens when row 1 failed to parse{owner}/{repo}: fix row 1 and rerun, because the empty string has a valid SHA-1 and would file this entry on a page nobody reads.
Done when the hosting
{owner}/{repo}and the full 40-character uppercase{HASH}are both known. - Repo:
-
Run
tools/worklog-target.sh "{HASH}" allandtools/worklog-target.sh fridayin parallel.allprintsPAGE=LOG_{HASH}andCONTENTS=LOG_CONTENTS;fridayprints the Friday of the current work week asyyyy-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 fromfridaymeans this machine'sdatedoes 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. -
Fill
templates/log-entry.mdwith the ten facts of this one task and save it to a file. Done when that file holds exactly one entry. -
Run
tools/worklog-pending.sh merge {HASH} {entry file}. It printsMERGED=(pending content in time order, then this task's entry),CLAIM=(the pending files it took) andPENDING=(how many). Pending content was written by an earlier stage that ended without a work log, so it belongs in this write.mergealso picks up the legacyH+ first 7 characters directory of the same{HASH}, so pending content stored under the previous hash rule still reaches the wiki.Exit Do 0 Carry on with MERGEDandCLAIM.PENDING=0is normal and still exit 01 A read or write under $JSC_HOME/worklog-pendingfailed. Stop and report the path from the message; nothing was deleted, so a rerun loses nothing2 The {HASH}is none of the three accepted shapes — 40 uppercase hex characters, 8 uppercase hex characters, orHplus 7 uppercase hex characters — or the entry file argument is missing. The last two are old pending directories left by the previous hash rule and are accepted only until the migration finishes. Pass the valuehash-idprinted, unshortened, and rerun from step 1Done when
MERGEDandCLAIMare known. 4.5. Runtools/worklog-pending.sh orphans. It takes no{HASH}and scans the whole pending area for directories that are not 40 uppercase hex characters. Exit 0 means there are none, so carry on silently. Exit 4 prints one{directory}<TAB>{file count}<TAB>{path}line per orphan: report every line to the user and carry on with this run — an orphan belongs to some other repository, so it never blocks this one.Why this runs at all: a directory the current rule cannot address holds work-log entries that no run will ever write, and nothing else reports them. "No pending content" and "pending content stranded under a name this rule cannot address" both reach the caller as success, so without this scan the entries stay invisible until somebody reads the directory by hand. Step 4 already adopts the one legacy shape that can be derived from
{HASH}; this scan covers every shape that cannot.Done when the scan exited 0, or its lines were reported to the user.
-
Read
PAGEfrom the LOG wiki repo of step 1 viajsc-gitea:wiki, and branch on the exit code the underlyinggitea.sh wiki-getreturned. Only exit 4 means the page is not there yet. Reading any other code as "it does not exist" builds a fresh page fromtemplates/log-entry.mdand 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 MERGEDcontent 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 code4 The page really is absent. Create it from the structure in templates/log-entry.md, then appendMERGED7 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 Before the write, hand every URL that
MERGEDcarries — plan page, work package heading, PR page — tojsc-gitea/tools/link-check.sh {url}.... It prints one{OK|DEAD|SKIP}<TAB>{url}<TAB>{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 entries 1 At least one link is DEAD. Write nothing, report the DEAD lines, and go to step 7 as a failure so the pending content survives 2 No URL reached the script. Pass the URLs and rerun 3 The list holds a Gitea URL but GITEA_HOSTis unset. Set it and rerun; never skip the check7 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 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
MERGEDis onPAGE, every entry that was already there is still there, and any 4 / 7 / 8 branch was followed as stated. -
Update
CONTENTSin the same pass, and letjsc-gitea/tools/wiki-contents.shdo the row work — never hand-edit the directory page.LOG_CONTENTSlives in the CONTENTS wiki repo thatgitea.sh wiki-repo CONTENTSresolves (JSC_WIKI_REPO_CONTENTS, thenJSC_WIKI_REPO), which is not the LOG repo of step 1 and never falls back to it. Because the two pages sit in different wikis, the row links the log page as[LOG_{HASH}](<url>), with<url>fromgitea.sh wiki-url <LOG repo> LOG_{HASH}— never the same-wiki[[...]]form, which resolves inside one wiki only and dead-links from here without reporting an error.wiki-urlexit 4 means the step 5 write has not landed yet, so stop and rerun step 5 before this one; exit 5 means the page carries nohtml_url, so stop and report it and never assemble the URL by hand; exit 7 or 8 means the token or the API failed, so stop and report that status.Put that URL through the step 5
link-check.shgate before the upsert, with the same exit branches: only exit 0 writes the row, and a DEAD line stops the write and goes to step 7 as a failure.Build one file holding the single row from
templates/log-contents.md— the absolute link, the bare{HASH}of step 1, this week's Friday date from step 2, the entry count and the update time — then run:jsc-gitea/tools/wiki-contents.sh upsert LOG 2 "{HASH}" {row file} templates/log-contents.mdThe key is column 2, the bare 40-character
{HASH}with no link markup around it. Column 1 carries the same page as a link for a human to click, and that link is exactly what must not be the key: it embeds the host and the encoded page name, so one change ofGITEA_HOST, one move ofJSC_WIKI_REPO_LOG, or one difference in how Gitea encodes the page name makes this run's cell differ from the last run's, the match fails, the row is appended, and the same log page now owns two rows of which the older is never updated again. The bare hash depends only on{owner}/{repo}. The script reads the whole page, replaces the matching row and appends when none matches, so every row that belongs to another log page stays as it was.Exit Do 0 The row is in place. It prints updatedoraddedplus the page it wrote — carry that word into the close-out1 The write failed, or the directory page holds no markdown table. Stop and report it as a failed write, and go to step 7 as a failure 2 An argument was rejected (unknown type, key column, missing row file). Fix the argument and rerun this step; nothing was written 3 No CONTENTS wiki repo is configured. Stop and report JSC_WIKI_REPO_CONTENTSandJSC_WIKI_REPOas the two variables to set, and go to step 7 as a failure. The log entry itself is onPAGEand stays there4 The directory page is absent and the script received no template. The call above always passes one, so this code means templates/log-contents.mdis 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 47 The key is invalid or lacks permission, so the other rows are unknown. Stop and report the key problem; the script wrote nothing, which is what keeps the other pages' rows alive 8 Some other API failure. Stop and report that status and retry only after the API is back Done when the run exited 0 and the row for
PAGEcarries this week's Friday date from step 2, or a non-zero code was reported and step 7 ran as a failure. -
Close the pending area on the result of steps 5 and 6:
tools/worklog-pending.sh commit {HASH} {CLAIM}after both succeeded, ortools/worklog-pending.sh abort {HASH} {CLAIM}after either failed.abortkeeps 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 PAGEare the thing to watch for2 The claim path is not the one mergeproduced, or it points outside this{HASH}'s pending directory. Rerun from step 4 with theCLAIMvalue thatmergeprinted; never pass a hand-written pathDone when one of the two ran and its exit code was reported.
-
Record how this run ended, as the very last thing this skill does:
jsc-hooks/tools/report-status.sh skill-end jsc-log:worklog {status} {exit} "{detail}"Resolve that path the way row 5 already resolves
jsc-hooks/hooks/session-timer.sh— 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 run whose result could not be recorded still had that result, and a work log that fails because the recorder is absent is worse than no recording.status This skill's case okSteps 5 and 6 both wrote and step 7 cleared the pending area. orphansexit 4 staysok: an orphan directory belongs to some other repository and changes nothing about this entry — carry its line count in{detail}so the count is on record even though the run passedblockedNothing could be written and nothing was: hash-idexit 1 (no SHA-1 helper on this machine),worklog-target.sh fridayexit 4 (no date arithmetic),wiki-repo LOGexit 3 (no LOG wiki repo configured), orlink-check.shexit 3 (GITEA_HOSTunset) or exit 7 (token rejected). The gate stopped the run before a page was toucheddegradedThe entry is on LOG_{HASH}but the close-out is short:wiki-contents.sh upsertreturned non-zero soLOG_CONTENTSstill carries the old row, orworklog-pending.sh commitexited 1 so the pending files survive a successful write and the next run merges them againfailedNothing reached the wiki after the run started working: link-check.shexit 1 stopped the write on a DEAD link, thePAGEread came back 7 or 8, orworklog-pending.sh mergeexited 1abortedThe user stopped the run, or the trigger turned out not to hold — no task ended here, so there is no entry to write and none was attempted {exit}is the exit code of whatever decided the status,0forok.{detail}is one short line well under 200 characters: counts and exit codes only, never entry text, page names, branch names, or personal data.Done when the command has run, or the script was absent and this step was skipped.