feat(link): 連結一律寫成 [文字](絕對網址),寫入前先驗證連得到
取消 [[頁名]] 與 [[顯示文字|頁名]] 兩種同 wiki 寫法,不再分「同存取庫」與 「跨存取庫」兩條規則。那種寫法只在自己那個 wiki 內解析,寫錯不報錯,畫面上 看起來像普通文字或死連結,巡不到也修不了。 連結寫進頁面前先過 jsc-gitea 的 link-check.sh,結束碼 0 才寫。驗證一律走 API, 不看網頁狀態碼:私有存取庫的網頁網址對未登入請求一律回 404,拿狀態碼判會把 好連結判成壞的。認證失敗回 7,與死連結的 1 分開,免得金鑰一過期就把還在的頁 整批判死。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: assistant
|
||||
description: 'Start, inspect, patrol or stop the background assistant: jsc-hooks/hooks/heartbeat.sh owns the freshness verdict, tools/schedule.sh the system scheduler, tools/patrol.sh one round. The heartbeat is written by a completed round and by nothing else, so the schedule carries the patrol entry only, its period from the heartbeat TTL; start runs one round then installs that entry - absolute CLI path, environment snapshot, unattended write confirmation, which cron lacks - status prints heartbeat, schedule and task book read-only, stop removes the entry before clearing the heartbeat. One round reads four independent sources - skill and chain usage, version gaps and the restart gate, SDLC stage and work-package locks, and the heartbeat''s own report - then rewrites wiki MONITOR_{HASH} through jsc-gitea:wiki as three fixed blocks - basic data untouched, the latest round replaced whole, a 24-row summary table - and upserts its MONITOR_CONTENTS row through jsc-gitea/tools/wiki-contents.sh, which reads the separate CONTENTS wiki repo and links the monitor page by its absolute wiki-url. A round that cannot record its result writes no heartbeat; one that starts while the previous holds the lock stands down. Use when someone starts, patrols or stops the assistant, or asks whether it runs and what is queued; not for environment health checks (jsc-cli:doctor), not for skill usage counts (jsc-log:stats).'
|
||||
description: 'Start, inspect, patrol or stop the background assistant: jsc-hooks/hooks/heartbeat.sh owns the freshness verdict, tools/schedule.sh the system scheduler, tools/patrol.sh one round. The heartbeat is written by a completed round and by nothing else, so the schedule carries the patrol entry only, its period from the heartbeat TTL; start runs one round then installs that entry - absolute CLI path, environment snapshot, unattended write confirmation, which cron lacks - status prints heartbeat, schedule and task book read-only, stop removes the entry before clearing the heartbeat. One round reads four independent sources - skill and chain usage, version gaps and the restart gate, SDLC stage and work-package locks, and the heartbeat''s own report - then rewrites wiki MONITOR_{HASH} through jsc-gitea:wiki as three fixed blocks - basic data untouched, the latest round replaced whole, a 24-row summary table - and upserts its MONITOR_CONTENTS row through jsc-gitea/tools/wiki-contents.sh, which reads the separate CONTENTS wiki repo and links the monitor page by its absolute wiki-url. Every link on either page is written as [text](URL) and is verified by jsc-gitea/tools/link-check.sh before that page is written, so a dead link stops the write instead of landing on the page. A round that cannot record its result writes no heartbeat; one that starts while the previous holds the lock stands down. Use when someone starts, patrols or stops the assistant, or asks whether it runs and what is queued; not for environment health checks (jsc-cli:doctor), not for skill usage counts (jsc-log:stats).'
|
||||
---
|
||||
|
||||
# assistant — start, status, patrol, stop
|
||||
@@ -26,15 +26,34 @@ Every tool below is addressed through `$JSC_HOME/current/{plugin}`, and `$JSC_HO
|
||||
| the heartbeat | `$JSC_HOME/current/jsc-hooks/hooks/heartbeat.sh` |
|
||||
| the wiki, through `jsc-gitea:wiki` | `$JSC_HOME/current/jsc-gitea/tools/gitea.sh` |
|
||||
| the `MONITOR_CONTENTS` row | `$JSC_HOME/current/jsc-gitea/tools/wiki-contents.sh` |
|
||||
| the link check every write depends on | `$JSC_HOME/current/jsc-gitea/tools/link-check.sh` |
|
||||
|
||||
**A `Skill(...)` rule permits invoking that skill and nothing more.** Every Bash call inside it is still checked on its own, so `jsc-gitea:wiki` reaching the wiki depends on `gitea.sh` carrying its own rule, and the directory row depends on `wiki-contents.sh` carrying one too — without them the round is refused locally, before any request leaves the machine, and the page never gets written.
|
||||
**A `Skill(...)` rule permits invoking that skill and nothing more.** Every Bash call inside it is still checked on its own, so `jsc-gitea:wiki` reaching the wiki depends on `gitea.sh` carrying its own rule, the directory row depends on `wiki-contents.sh` carrying one too, and both writes depend on `link-check.sh` carrying one — without them the round is refused locally, before any request leaves the machine, and the page never gets written.
|
||||
|
||||
**Never build a tool path out of the base directory the CLI hands you in the skill prompt.** That directory points into the plugin cache and carries a version segment, and the permission gate allows exactly the five paths above and nothing else. A cache path is therefore refused silently: the round stops on a permission prompt nobody can answer, records nothing, writes no heartbeat, and the refusal looks exactly like a broken tool. Read the paths off this table every time — not off the prompt, not off a previous transcript, not off `crontab -l`.
|
||||
**Never build a tool path out of the base directory the CLI hands you in the skill prompt.** That directory points into the plugin cache and carries a version segment, and the permission gate allows exactly the six paths above and nothing else. A cache path is therefore refused silently: the round stops on a permission prompt nobody can answer, records nothing, writes no heartbeat, and the refusal looks exactly like a broken tool. Read the paths off this table every time — not off the prompt, not off a previous transcript, not off `crontab -l`.
|
||||
|
||||
Both scripts check this for themselves: run from anywhere outside `$JSC_HOME/current`, they print a `[WARN]` line on stderr naming the path they were started from and the path they should have been started from, and then carry on. That line means this round is on the wrong path — quote it, fix the path, and do not treat the round's success as proof that the path was fine.
|
||||
|
||||
`current` is a set of version-free links that `jsc-cli:deploy` maintains, so an upgrade moves the cache and leaves these paths alone. When one of them is missing, report the missing link and say `jsc-cli:deploy` has to run; never fall back to a cache path to get the round through, and never create the link here.
|
||||
|
||||
## Two rules bind every link this skill writes
|
||||
|
||||
Both pages this round writes carry links, and both rules below hold for every one of them — the monitor page and the directory row alike.
|
||||
|
||||
**Rule A — a link is always written as `[{text}]({URL})`.** The wiki's own `[[page]]` and `[[text|page]]` forms are not used here at all, and neither is the split between "same repo" and "cross repo" writing. The URL comes from `$JSC_HOME/current/jsc-gitea/tools/gitea.sh wiki-url {repo} {page}`; never assemble a path by hand. `[[...]]` resolves only inside the wiki it sits in: the monitor page and the directory page live in two different repos, so a `[[MONITOR_{HASH}]]` written into the directory row renders as an ordinary-looking link that goes nowhere, and nothing reports it.
|
||||
|
||||
**Rule B — a link is verified before it is written, never after.** Collect every link that is about to go into the page, hand the whole set to `$JSC_HOME/current/jsc-gitea/tools/link-check.sh`, and write only on exit 0. The script prints one `{OK|DEAD|SKIP}<TAB>{URL}<TAB>{note}` line per URL and checks Gitea URLs through the API, never through the web status code — a private repo answers 404 to a logged-out web request, so a status-code check condemns live pages.
|
||||
|
||||
| Exit | Meaning | Do |
|
||||
| --- | --- | --- |
|
||||
| 0 | every link resolves | write the page |
|
||||
| 1 | at least one link is dead | write nothing, report the `DEAD` lines verbatim, and take the step's own abort row |
|
||||
| 2 | usage error — no URL was given | a defect in the call: pass the URLs and run it once more |
|
||||
| 3 | a Gitea URL is in the list but `GITEA_HOST` is unset | report the variable and stop; never skip the check to get the write through |
|
||||
| 7 | Gitea authentication failed (401, 403) | stop and report it as a token problem, never as dead links — an expired token makes live private pages look missing |
|
||||
|
||||
A round with no link to write skips the call and says so; a round that cannot verify writes nothing.
|
||||
|
||||
## Pick the operation
|
||||
|
||||
Run exactly one operation per invocation. Take it from the request: starting, launching or waking the assistant is `start`; asking whether it runs, what it is doing, or what is queued is `status`; running one round, patrolling, or a scheduled wake-up is `patrol`; stopping, halting or shutting it down is `stop`. When the request names none of the four, or names more than one, ask through the `jsc-ask:ask` decision tree with those four as the options, each stating its effect — `start` runs one round and installs the scheduled entry that keeps running rounds, `status` changes nothing, `patrol` runs one round and writes one heartbeat, `stop` removes that entry and deletes the heartbeat. **The one exception: a `patrol` invocation never asks anything at all** (see 界線 1 below). Never guess, and never run a second operation the caller did not ask for. Completion condition: exactly one of `start`, `status`, `patrol`, `stop` is chosen and named in the report.
|
||||
@@ -184,11 +203,15 @@ One round: read four sources, record the result, then beat. Everything before th
|
||||
| 最新一輪 | the whole content of `latest_file`, replacing the old block entirely |
|
||||
| 近 24 輪摘要 | `summary_file`, which already holds the heading, the five-column table header (`巡檢時間`、`本輪判定`、`四項成敗`、`待人處理`、`警示來源`) and this round's row; then the old table's data rows in their old order underneath, cut so the table holds at most 24 rows |
|
||||
|
||||
Put the whole page. An old-format page — per-round sections stacked up, no summary table — has no rows to carry over: keep its `本頁基本資料` block, drop the stacked sections, let the table start with this round's row, and say in the report that the page was converted. Only exit 4 from the read permits creating the page instead, and then the body is the whole content of `newpage_file`, which already carries all three blocks. Exit 7 and exit 8 mean the old content is unknown: create nothing, write nothing — rebuilding a page from an unknown original throws the summary table away. On any write failure — including exit 3 with no wiki repo configured for `MONITOR`, which the patrol cannot ask about — run `$JSC_HOME/current/jsc-assist/tools/patrol.sh abort --round {round}`, report the code, and stop. **No record, no heartbeat**, and that verdict belongs to this step alone: the round's result lives on this page, so a repo this step cannot resolve leaves the round with nowhere to be recorded. Step 4 is judged on its own terms. Completion condition: the put or the create returned success and the page holds exactly three blocks with the summary table at 24 rows or fewer and this round's row on top, or the abort ran and the round was reported as unrecorded with its exit code.
|
||||
**Verify the page's links before the write.** List every link the rebuilt body carries — the ones the latest-round block brought in, and any that survived in the block carried over from the old page — and run `$JSC_HOME/current/jsc-gitea/tools/link-check.sh` over the whole list. Exit 0 is the only result that permits the write. On exit 1 report the `DEAD` lines verbatim, then run `$JSC_HOME/current/jsc-assist/tools/patrol.sh abort --round {round}` and stop: a round that writes a dead link records a false trail nobody can follow back. Exits 2, 3 and 7 take the same abort, each reported by the rule B table above. A body carrying no link at all needs no call — say so in the report rather than claiming a check that never ran.
|
||||
|
||||
Put the whole page. An old-format page — per-round sections stacked up, no summary table — has no rows to carry over: keep its `本頁基本資料` block, drop the stacked sections, let the table start with this round's row, and say in the report that the page was converted. Only exit 4 from the read permits creating the page instead, and then the body is the whole content of `newpage_file`, which already carries all three blocks. Exit 7 and exit 8 mean the old content is unknown: create nothing, write nothing — rebuilding a page from an unknown original throws the summary table away. On any write failure — including exit 3 with no wiki repo configured for `MONITOR`, which the patrol cannot ask about — run `$JSC_HOME/current/jsc-assist/tools/patrol.sh abort --round {round}`, report the code, and stop. **No record, no heartbeat**, and that verdict belongs to this step alone: the round's result lives on this page, so a repo this step cannot resolve leaves the round with nowhere to be recorded. Step 4 is judged on its own terms. Completion condition: `link-check.sh` exited 0 over the body's links or the body carried none, the put or the create returned success, and the page holds exactly three blocks with the summary table at 24 rows or fewer and this round's row on top, or the abort ran and the round was reported as unrecorded with its exit code.
|
||||
|
||||
4. **Update this machine's row in `MONITOR_CONTENTS`, through `jsc-gitea/tools/wiki-contents.sh`.** That page is a directory every machine writes to, and it lives in the repo `gitea.sh wiki-repo CONTENTS` resolves — `JSC_WIKI_REPO_CONTENTS`, then `JSC_WIKI_REPO`, then exit 3, and never a fallback to `JSC_WIKI_REPO_MONITOR`. The script owns the read-match-write of one row, so never read this page and rebuild it by hand, never write it through `jsc-gitea:wiki`, and never rebuild it the way step 3 rebuilds the content page — every other row here belongs to a machine that is not this one, and one careless whole-page write deletes their records.
|
||||
|
||||
**Finish the row first.** The `row=` line in `contents_file` carries the placeholder `{監控頁絕對網址}` in its first cell, because the directory page and the monitor page now sit in two different wikis: `[[MONITOR_{HASH}]]` resolves only inside one wiki and would dead-link from here while still looking like a link, and the absolute URL cannot be known until step 3 has actually put the page. Run `$JSC_HOME/current/jsc-gitea/tools/gitea.sh wiki-url {the MONITOR repo step 3 resolved} MONITOR_{HASH}`, replace the placeholder with what it prints, and write the finished row to a file. Exit 4 there means step 3's write has not landed — go back to step 3 rather than writing a row. Exit 5 means the page carries no `html_url`: report it and never assemble a URL by hand. Exit 7 or 8: report the code and take the abort row below. **Any other non-zero exit takes the same abort row**, a missing argument included — a URL that never arrived would otherwise leave the link cell holding the raw placeholder, and the row would still be written.
|
||||
**Finish the row first.** The `row=` line in `contents_file` already carries the rule A shape `[{page name}]({URL})` in its first cell, with the placeholder `{監控頁絕對網址}` standing in for the URL, because the absolute URL cannot be known until step 3 has actually put the page. Run `$JSC_HOME/current/jsc-gitea/tools/gitea.sh wiki-url {the MONITOR repo step 3 resolved} MONITOR_{HASH}`, replace the placeholder with what it prints, and write the finished row to a file. Exit 4 there means step 3's write has not landed — go back to step 3 rather than writing a row. Exit 5 means the page carries no `html_url`: report it and never assemble a URL by hand. Exit 7 or 8: report the code and take the abort row below. **Any other non-zero exit takes the same abort row**, a missing argument included — a URL that never arrived would otherwise leave the link cell holding the raw placeholder, and the row would still be written.
|
||||
|
||||
**Then verify that URL before the row goes anywhere.** Run `$JSC_HOME/current/jsc-gitea/tools/link-check.sh {the URL just substituted}` and read the exit code by the rule B table above. Exit 0 is the only result that permits the upsert. On exit 1 the directory would gain a row pointing at a page that is not there: report the `DEAD` line verbatim, write no row, and treat the directory row as not updated — the round's own result is already on `MONITOR_{HASH}`, so carry on to step 5 and write the heartbeat, exactly as exit 3 from the upsert does, and put the dead link into the 待人處理 rows. Exits 2, 3 and 7 are reported the same way and the row is left unwritten. Never write the row first and check afterwards: the directory is what other people read to find this machine, and a dead row there sends every one of them to a page that does not exist.
|
||||
|
||||
Then run, with the template as the fifth argument every time:
|
||||
|
||||
@@ -206,11 +229,11 @@ One round: read four sources, record the result, then beat. Everything before th
|
||||
| 7 | The token is invalid or lacks permission, so the other machines' rows are unknown. The script wrote nothing, which is what keeps those rows alive. Abort, report the key problem, and stop |
|
||||
| 8 | Some other API failure. Abort, report the status, and stop |
|
||||
|
||||
Completion condition: the script exited 0 and exactly one row carries this machine's bare `HASH` in column 2 with this round's values, or exit 3 was reported as an unwritten directory row and the round carried on, or one of the other non-zero codes — `wiki-url`'s included — was reported after the abort ran.
|
||||
Completion condition: `link-check.sh` exited 0 over the row's URL and the script exited 0 with exactly one row carrying this machine's bare `HASH` in column 2 with this round's values, or exit 3 from the upsert or a non-zero `link-check.sh` was reported as an unwritten directory row and the round carried on, or one of the other non-zero codes — `wiki-url`'s included — was reported after the abort ran.
|
||||
|
||||
5. **Write the heartbeat.** Run `$JSC_HOME/current/jsc-assist/tools/patrol.sh finish --round {round}`. This is the last step for a reason: it is the only thing that turns a fresh heartbeat into a true statement. Judge the exit code by the patrol.sh table — 2, 4 and 5 all mean the round is recorded but unproven, and each has its own report line there. Completion condition: `finish` exited 0, or the failure was reported as "recorded but no heartbeat" with its code.
|
||||
|
||||
6. **Report the round.** Print the round verdict and, when it is `警示`, the `warn_sources=` text that says why — a round can read all four sources and still come out `警示`, and that column is the only place the reason appears; then one line per item with its `status=` and, for a failure, its `note=`; the monitor page name, and the directory row as `updated`, `added`, or not written with the exit code and the reason; whether the heartbeat was written; and, when `lock_broken=1`, that the previous round's lock was taken over because it had aged past the TTL. Close with the 待人處理 rows from the latest-round block, verbatim, and nothing else — the patrol names an entry point and stops there. Completion condition: all four items appear in the report, the heartbeat outcome is stated as written or not written, and no suggestion in 待人處理 was acted on.
|
||||
6. **Report the round.** Print the round verdict and, when it is `警示`, the `warn_sources=` text that says why — a round can read all four sources and still come out `警示`, and that column is the only place the reason appears; then one line per item with its `status=` and, for a failure, its `note=`; the monitor page name, the link-check verdict for each of the two writes — passed, skipped for a body with no link, or refused with its exit code and its `DEAD` lines — and the directory row as `updated`, `added`, or not written with the exit code and the reason; whether the heartbeat was written; and, when `lock_broken=1`, that the previous round's lock was taken over because it had aged past the TTL. Close with the 待人處理 rows from the latest-round block, verbatim, and nothing else — the patrol names an entry point and stops there. Completion condition: all four items appear in the report, the heartbeat outcome is stated as written or not written, and no suggestion in 待人處理 was acted on.
|
||||
|
||||
## status
|
||||
|
||||
|
||||
Reference in New Issue
Block a user