wherdr 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{AgentView.C4YOwvZG.css → AgentView.DzFWBd2A.css} +1 -1
- package/.output/public/_nuxt/B4Yk2tB1.js +23 -0
- package/.output/public/_nuxt/BBw_0a4M.js +6 -0
- package/.output/public/_nuxt/{D7sxNXx1.js → BSy6OCIY.js} +1 -1
- package/.output/public/_nuxt/BX5PM2JQ.js +1 -0
- package/.output/public/_nuxt/BgPDnf2S.js +1 -0
- package/.output/public/_nuxt/BkOxQzbS.js +2 -0
- package/.output/public/_nuxt/{8ksjcuDu.js → CKhgznq4.js} +45 -56
- package/.output/public/_nuxt/{Cd8j9O5w.js → CP8o925U.js} +1 -1
- package/.output/public/_nuxt/CkbmecwS.js +1 -0
- package/.output/public/_nuxt/Cr3SrGky.js +1 -0
- package/.output/public/_nuxt/DAg9fiVh.js +89 -0
- package/.output/public/_nuxt/DT8qZ0Wv.js +1 -0
- package/.output/public/_nuxt/DaaJIRB3.js +1 -0
- package/.output/public/_nuxt/{DViwOclv.js → DcJi16XA.js} +1 -1
- package/.output/public/_nuxt/VpjCgSXu.js +36 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/c735b6c0-1bd0-47d7-a208-d310f0a06287.json +1 -0
- package/.output/public/_nuxt/dPxB2L94.js +89 -0
- package/.output/public/_nuxt/{entry.Crt1r4l3.css → entry.CGWKmYU3.css} +1 -1
- package/.output/public/_nuxt/xYX85SPI.js +1 -0
- package/.output/public/sw.js +1 -1
- package/.output/server/chunks/_/branding.mjs +1 -1
- package/.output/server/chunks/_/filePaths.mjs +25 -0
- package/.output/server/chunks/_/files.mjs +204 -0
- package/.output/server/chunks/_/machineOrder.mjs +1 -1
- package/.output/server/chunks/_/onboarding.mjs +1 -1
- package/.output/server/chunks/_/package.mjs +3 -2
- package/.output/server/chunks/_/pluginPolicy.mjs +93 -0
- package/.output/server/chunks/_/plugins.mjs +4 -94
- package/.output/server/chunks/_/projectBoard.mjs +120 -29
- package/.output/server/chunks/_/projectDocs.mjs +106 -0
- package/.output/server/chunks/_/projectInbox.mjs +135 -0
- package/.output/server/chunks/_/projectThreads.mjs +46 -0
- package/.output/server/chunks/_/projectThreads2.mjs +61 -0
- package/.output/server/chunks/_/projectsActions.mjs +1 -1
- package/.output/server/chunks/_/projectsPlugin.mjs +1 -1
- package/.output/server/chunks/_/restart.mjs +2 -312
- package/.output/server/chunks/_/reveal.mjs +2 -24
- package/.output/server/chunks/_/selfupdate.mjs +3 -2
- package/.output/server/chunks/_/updates.mjs +51 -10
- package/.output/server/chunks/_/worktrees.mjs +1 -1
- package/.output/server/chunks/_/ws.mjs +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +1096 -268
- package/.output/server/chunks/raw/bump-homebrew.mjs +1 -1
- package/.output/server/chunks/raw/herdr-plugin.mjs +1 -1
- package/.output/server/chunks/raw/release-ready.mjs +5 -0
- package/.output/server/chunks/routes/api/_..._.mjs +1 -1
- package/.output/server/chunks/routes/api/agents.post.mjs +1 -1
- package/.output/server/chunks/routes/api/ask-tab.post.mjs +1 -1
- package/.output/server/chunks/routes/api/attach.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/lock-all.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/lock.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/login/options.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/login/verify.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/max-session.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/register/options.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/register/verify.post.mjs +1 -1
- package/.output/server/chunks/routes/api/auth/status.get.mjs +1 -1
- package/.output/server/chunks/routes/api/board.get.mjs +147 -0
- package/.output/server/chunks/routes/api/changes.get.mjs +2 -2
- package/.output/server/chunks/routes/api/chat/image.get.mjs +2 -2
- package/.output/server/chunks/routes/api/chat.get.mjs +2 -2
- package/.output/server/chunks/routes/api/choose.post.mjs +29 -3
- package/.output/server/chunks/routes/api/claude-statusline.post.mjs +2 -2
- package/.output/server/chunks/routes/api/close/status.get.mjs +2 -2
- package/.output/server/chunks/routes/api/close.post.mjs +1 -1
- package/.output/server/chunks/routes/api/codex-update.post.mjs +2 -2
- package/.output/server/chunks/routes/api/commands.get.mjs +2 -2
- package/.output/server/chunks/routes/api/config.get.mjs +3 -2
- package/.output/server/chunks/routes/api/dirs.get.mjs +2 -2
- package/.output/server/chunks/routes/api/dismiss-stopped.post.mjs +43 -0
- package/.output/server/chunks/routes/api/dismiss.post.mjs +2 -2
- package/.output/server/chunks/routes/api/effort.post.mjs +2 -2
- package/.output/server/chunks/routes/api/efforts.get.mjs +2 -2
- package/.output/server/chunks/routes/api/file.post.mjs +2 -2
- package/.output/server/chunks/routes/api/files/download.get.mjs +53 -0
- package/.output/server/chunks/routes/api/files/read.get.mjs +45 -0
- package/.output/server/chunks/routes/api/files.get.mjs +44 -0
- package/.output/server/chunks/routes/api/gitroot.get.mjs +2 -2
- package/.output/server/chunks/routes/api/health.get.mjs +1 -1
- package/.output/server/chunks/routes/api/index.get.mjs +1 -1
- package/.output/server/chunks/routes/api/input.post.mjs +13 -3
- package/.output/server/chunks/routes/api/interrupt.post.mjs +2 -2
- package/.output/server/chunks/routes/api/isgit.get.mjs +2 -2
- package/.output/server/chunks/routes/api/machine/awake.get.mjs +2 -2
- package/.output/server/chunks/routes/api/machine/awake.post.mjs +2 -2
- package/.output/server/chunks/routes/api/machine/order.get.mjs +1 -1
- package/.output/server/chunks/routes/api/machine/order.post.mjs +1 -1
- package/.output/server/chunks/routes/api/machine/rename.post.mjs +2 -2
- package/.output/server/chunks/routes/api/menu.post.mjs +2 -2
- package/.output/server/chunks/routes/api/model.post.mjs +2 -2
- package/.output/server/chunks/routes/api/models.get.mjs +2 -2
- package/.output/server/chunks/routes/api/nav.post.mjs +5 -4
- package/.output/server/chunks/routes/api/onboarding.get.mjs +1 -1
- package/.output/server/chunks/routes/api/onboarding.post.mjs +1 -1
- package/.output/server/chunks/routes/api/phone.get.mjs +2 -2
- package/.output/server/chunks/routes/api/phone.post.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/actions.get.mjs +2 -1
- package/.output/server/chunks/routes/api/plugins/invoke.post.mjs +2 -1
- package/.output/server/chunks/routes/api/plugins/profiles.get.mjs +2 -1
- package/.output/server/chunks/routes/api/plugins/projects.get.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/projects.post.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/thread-limits.get.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/thread-limits.post.mjs +2 -2
- package/.output/server/chunks/routes/api/project/inbox.get.mjs +43 -0
- package/.output/server/chunks/routes/api/project/inbox.post.mjs +43 -0
- package/.output/server/chunks/routes/api/project/memory.get.mjs +42 -0
- package/.output/server/chunks/routes/api/project/report.get.mjs +2 -2
- package/.output/server/chunks/routes/api/project/routines.get.mjs +43 -0
- package/.output/server/chunks/routes/api/project/settings.get.mjs +41 -0
- package/.output/server/chunks/routes/api/project/thread.get.mjs +44 -0
- package/.output/server/chunks/routes/api/project/thread.post.mjs +45 -0
- package/.output/server/chunks/routes/api/prompt.post.mjs +21 -3
- package/.output/server/chunks/routes/api/push/quiet.post.mjs +2 -2
- package/.output/server/chunks/routes/api/push/subscribe.post.mjs +2 -2
- package/.output/server/chunks/routes/api/push/test.post.mjs +2 -2
- package/.output/server/chunks/routes/api/push/unsubscribe.post.mjs +2 -2
- package/.output/server/chunks/routes/api/quotas.get.mjs +2 -2
- package/.output/server/chunks/routes/api/rename.post.mjs +1 -1
- package/.output/server/chunks/routes/api/requeue.post.mjs +2 -2
- package/.output/server/chunks/routes/api/restart.get.mjs +1 -1
- package/.output/server/chunks/routes/api/restart.post.mjs +1 -1
- package/.output/server/chunks/routes/api/reveal.post.mjs +2 -1
- package/.output/server/chunks/routes/api/screen-key.post.mjs +2 -2
- package/.output/server/chunks/routes/api/screen.get.mjs +5 -3
- package/.output/server/chunks/routes/api/search.get.mjs +2 -2
- package/.output/server/chunks/routes/api/seen.post.mjs +2 -2
- package/.output/server/chunks/routes/api/sessions.get.mjs +2 -2
- package/.output/server/chunks/routes/api/sessions.post.mjs +2 -2
- package/.output/server/chunks/routes/api/space.post.mjs +14 -14
- package/.output/server/chunks/routes/api/state.get.mjs +2 -2
- package/.output/server/chunks/routes/api/unqueue.post.mjs +2 -2
- package/.output/server/chunks/routes/api/update/status.get.mjs +1 -1
- package/.output/server/chunks/routes/api/update.get.mjs +2 -2
- package/.output/server/chunks/routes/api/update.post.mjs +2 -2
- package/.output/server/chunks/routes/api/upload.post.mjs +2 -2
- package/.output/server/chunks/routes/api/worktrees/remove.post.mjs +1 -1
- package/.output/server/chunks/routes/api/worktrees.get.mjs +1 -1
- package/.output/server/chunks/routes/favicon.ico.get.mjs +1 -1
- package/.output/server/chunks/routes/icons/_name_.get.mjs +2 -2
- package/.output/server/chunks/routes/renderer.mjs +3 -3
- package/.output/server/chunks/routes/uploads/_...name_.get.mjs +2 -2
- package/.output/server/chunks/routes/ws/_..._.mjs +2 -2
- package/.output/server/chunks/routes/ws/events.mjs +2 -2
- package/.output/server/chunks/routes/ws/term.mjs +2 -2
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/index.mjs +2 -2
- package/.output/server/package.json +1 -1
- package/README.md +332 -25
- package/bin/install-docker-skill.sh +20 -0
- package/bin/lib/commands.mjs +5 -1
- package/bin/lib/core.mjs +1 -0
- package/bin/lib/install-skill.sh +100 -0
- package/bin/lib/skill.mjs +16 -0
- package/bin/lib/updater.mjs +65 -12
- package/bin/lib/verify-release.mjs +20 -0
- package/package.json +3 -2
- package/skills/wherdr/SKILL.md +180 -0
- package/.output/public/_nuxt/B7QP1TFs.js +0 -23
- package/.output/public/_nuxt/BDNMzG2s.js +0 -1
- package/.output/public/_nuxt/BOpBIziZ.js +0 -6
- package/.output/public/_nuxt/BWXdFfeg.js +0 -17
- package/.output/public/_nuxt/COFCNFpG.js +0 -83
- package/.output/public/_nuxt/CXG-cOdh.js +0 -1
- package/.output/public/_nuxt/CkZ8t6uB.js +0 -1
- package/.output/public/_nuxt/D4Yz4rat.js +0 -65
- package/.output/public/_nuxt/DoD7Qb5P.js +0 -30
- package/.output/public/_nuxt/DyBZDSCV.js +0 -2
- package/.output/public/_nuxt/bLP7Jj-b.js +0 -1
- package/.output/public/_nuxt/builds/meta/7afbf062-5369-4002-b20d-7a582ff6e0f1.json +0 -1
- package/.output/server/chunks/routes/ws/mirror.mjs +0 -161
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Automatic startup is silent and best effort; explicit operations report errors.
|
|
3
|
+
# Usage: sh bin/lib/install-skill.sh [auto|refresh|install|uninstall] [package root]
|
|
4
|
+
install_skill() (
|
|
5
|
+
set -eu
|
|
6
|
+
[ -n "${HOME:-}" ] && [ -d "$HOME" ] || exit 0
|
|
7
|
+
root=${2:-$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd -P)}
|
|
8
|
+
mode=${1:-auto}
|
|
9
|
+
base=${WHERDR_DIR:-$HOME/wherdr}
|
|
10
|
+
case "$base" in /*) ;; *) base="$PWD/$base" ;; esac
|
|
11
|
+
dest="$base/skills/wherdr"
|
|
12
|
+
state="$HOME/.local/state/wherdr"
|
|
13
|
+
disabled="$state/skill-disabled"
|
|
14
|
+
claude=${CLAUDE_CONFIG_DIR:-$HOME/.claude}
|
|
15
|
+
case "$claude" in /*) ;; *) claude="$HOME/$claude" ;; esac
|
|
16
|
+
dirs() {
|
|
17
|
+
[ ! -d "$claude" ] || printf '%s\n' "$claude/skills"
|
|
18
|
+
if [ -d "$HOME/.agents" ] || [ -d "$HOME/.codex" ] || [ -d "$HOME/.omp" ]; then
|
|
19
|
+
printf '%s\n' "$HOME/.agents/skills"
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
# Opt-out applies even to an explicit install; uninstall is always available.
|
|
23
|
+
if [ "$mode" != uninstall ]; then
|
|
24
|
+
[ "${WHERDR_SKILL:-}" != off ] || { echo 'wherdr skill: disabled by WHERDR_SKILL=off'; exit 0; }
|
|
25
|
+
[ "$mode" = install ] || [ ! -e "$disabled" ] || exit 0
|
|
26
|
+
[ -n "$(dirs)" ] || { echo 'wherdr skill: no installed agent found'; exit 0; }
|
|
27
|
+
fi
|
|
28
|
+
if [ "$mode" = uninstall ]; then
|
|
29
|
+
mkdir -p "$state"
|
|
30
|
+
[ ! -L "$disabled" ] || { echo 'wherdr skill: opt-out marker is a symlink' >&2; exit 1; }
|
|
31
|
+
[ -f "$disabled" ] || : > "$disabled"
|
|
32
|
+
fi
|
|
33
|
+
# Never follow a foreign symlink at the managed copy or its metadata.
|
|
34
|
+
[ ! -L "$dest" ] && [ ! -L "$dest/SKILL.md" ] && [ ! -L "$dest/.wherdr-managed" ] || {
|
|
35
|
+
echo 'wherdr skill: foreign symlink left alone' >&2; exit 0;
|
|
36
|
+
}
|
|
37
|
+
owned=false
|
|
38
|
+
[ ! -f "$dest/.wherdr-managed" ] || owned=true
|
|
39
|
+
if [ -e "$dest" ] && [ "$owned" = false ]; then
|
|
40
|
+
# Adopt an older plugin copy only when an agent links to this exact path.
|
|
41
|
+
legacy=$(dirs | while IFS= read -r d; do
|
|
42
|
+
if [ -L "$d/wherdr" ] && [ "$(readlink "$d/wherdr")" = "$dest" ]; then echo 1; fi
|
|
43
|
+
done)
|
|
44
|
+
[ -z "$legacy" ] || owned=true
|
|
45
|
+
[ "$owned" = true ] || { echo 'wherdr skill: existing foreign copy left alone'; exit 0; }
|
|
46
|
+
fi
|
|
47
|
+
case "$mode" in
|
|
48
|
+
auto|refresh|install)
|
|
49
|
+
[ -f "$root/skills/wherdr/SKILL.md" ] || { echo 'wherdr skill: bundled SKILL.md is missing' >&2; exit 1; }
|
|
50
|
+
mkdir -p "$dest"
|
|
51
|
+
if ! cmp -s "$root/skills/wherdr/SKILL.md" "$dest/SKILL.md"; then
|
|
52
|
+
tmp=$(mktemp "$dest/.SKILL.XXXXXX")
|
|
53
|
+
trap 'rm -f "$tmp"' EXIT HUP INT TERM
|
|
54
|
+
cp "$root/skills/wherdr/SKILL.md" "$tmp"
|
|
55
|
+
chmod 644 "$tmp"
|
|
56
|
+
mv -f "$tmp" "$dest/SKILL.md"
|
|
57
|
+
fi
|
|
58
|
+
[ -f "$dest/.wherdr-managed" ] || : > "$dest/.wherdr-managed"
|
|
59
|
+
dirs | while IFS= read -r d; do
|
|
60
|
+
link="$d/wherdr"
|
|
61
|
+
record="$d/.wherdr-managed-target"
|
|
62
|
+
# The record survives removal of an old WHERDR_DIR. A path's shape
|
|
63
|
+
# alone never proves ownership of a dangling link.
|
|
64
|
+
if [ -L "$link" ] && [ ! -e "$link" ] && [ -f "$record" ] && [ ! -L "$record" ] \
|
|
65
|
+
&& [ "$(cat "$record")" = "$(readlink "$link")" ]; then
|
|
66
|
+
rm "$link"
|
|
67
|
+
fi
|
|
68
|
+
if [ ! -e "$link" ] && [ ! -L "$link" ]; then
|
|
69
|
+
mkdir -p "$d"
|
|
70
|
+
ln -s "$dest" "$link"
|
|
71
|
+
fi
|
|
72
|
+
if [ -L "$link" ] && [ "$(readlink "$link")" = "$dest" ] && [ ! -L "$record" ]; then
|
|
73
|
+
if [ ! -f "$record" ] || [ "$(cat "$record")" != "$dest" ]; then printf '%s\n' "$dest" > "$record"; fi
|
|
74
|
+
fi
|
|
75
|
+
done
|
|
76
|
+
[ "$mode" != install ] || rm -f "$disabled"
|
|
77
|
+
echo 'wherdr skill: installed or already up to date'
|
|
78
|
+
;;
|
|
79
|
+
uninstall)
|
|
80
|
+
dirs | while IFS= read -r d; do
|
|
81
|
+
if [ -L "$d/wherdr" ] && [ "$(readlink "$d/wherdr")" = "$dest" ]; then
|
|
82
|
+
rm "$d/wherdr"
|
|
83
|
+
[ -L "$d/.wherdr-managed-target" ] || rm -f "$d/.wherdr-managed-target"
|
|
84
|
+
fi
|
|
85
|
+
done
|
|
86
|
+
if [ "$owned" = true ]; then
|
|
87
|
+
rm -f "$dest/SKILL.md" "$dest/.wherdr-managed"
|
|
88
|
+
rmdir "$dest" "$(dirname "$dest")" 2>/dev/null || true
|
|
89
|
+
fi
|
|
90
|
+
echo 'wherdr skill: uninstalled; automatic installation disabled'
|
|
91
|
+
;;
|
|
92
|
+
*) echo 'usage: install-skill.sh [auto|refresh|install|uninstall] [package root]' >&2; exit 1 ;;
|
|
93
|
+
esac
|
|
94
|
+
)
|
|
95
|
+
if [ "${1:-auto}" = auto ]; then
|
|
96
|
+
( install_skill "$@" ) >/dev/null 2>&1
|
|
97
|
+
exit 0
|
|
98
|
+
else
|
|
99
|
+
install_skill "$@"
|
|
100
|
+
fi
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The shell helper is also used without Node by the Docker / Herdr installer.
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
export function refreshSkill(env = process.env) {
|
|
6
|
+
// One-shot handoff from the detached updater; never pass it to the server.
|
|
7
|
+
const refreshed = env.WHERDR_SKILL_REFRESHED === '1'
|
|
8
|
+
delete env.WHERDR_SKILL_REFRESHED
|
|
9
|
+
delete process.env.WHERDR_SKILL_REFRESHED
|
|
10
|
+
if (refreshed || env.WHERDR_SKILL === 'off') return
|
|
11
|
+
try {
|
|
12
|
+
spawnSync('sh', [fileURLToPath(new URL('./install-skill.sh', import.meta.url))], {
|
|
13
|
+
env, stdio: 'ignore', timeout: 5000,
|
|
14
|
+
})
|
|
15
|
+
} catch {} // Optional: a read-only home or missing shell must not prevent startup.
|
|
16
|
+
}
|
package/bin/lib/updater.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// The server copies this file out of the package before running it (npm and
|
|
11
11
|
// Homebrew replace the package folder): it imports Node built-ins only.
|
|
12
|
+
import { createHash } from 'node:crypto'
|
|
12
13
|
import { spawn } from 'node:child_process'
|
|
13
14
|
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
14
15
|
import path from 'node:path'
|
|
@@ -38,10 +39,16 @@ function pluginDriver(plan, io) {
|
|
|
38
39
|
const staged = `${plan.root}.new`
|
|
39
40
|
const previous = `${plan.root}.prev`
|
|
40
41
|
return {
|
|
41
|
-
// The
|
|
42
|
+
// The checksummed GitHub package, unpacked next to the running copy.
|
|
42
43
|
async install() {
|
|
43
44
|
const tgz = path.join(plan.dir, 'update', `wherdr-${plan.to}.tgz`)
|
|
44
|
-
|
|
45
|
+
if (!plan.releaseBase) throw new Error('Release source is missing from the update plan')
|
|
46
|
+
if (!/^\d+\.\d+\.\d+$/.test(plan.to)) throw new Error('Invalid release version')
|
|
47
|
+
const url = `${plan.releaseBase}/v${plan.to}/wherdr-${plan.to}.tgz`
|
|
48
|
+
await io.download(url, tgz)
|
|
49
|
+
await io.download(`${url}.sha256`, `${tgz}.sha256`)
|
|
50
|
+
await io.verify(tgz, `${tgz}.sha256`)
|
|
51
|
+
io.rm(`${tgz}.sha256`)
|
|
45
52
|
io.rm(staged)
|
|
46
53
|
io.mkdir(staged)
|
|
47
54
|
await io.run('tar', ['-xzf', tgz, '-C', staged, '--strip-components=1'])
|
|
@@ -107,13 +114,28 @@ function brewDriver(plan, io) {
|
|
|
107
114
|
if (failed !== keg && path.dirname(failed) === path.dirname(keg)) io.rm(failed)
|
|
108
115
|
await io.run(brew, ['link', '--overwrite', 'wherdr'], env)
|
|
109
116
|
},
|
|
110
|
-
start
|
|
117
|
+
async start() {
|
|
118
|
+
// The service's CLI refreshes its own package. No opt lookup or duplicate
|
|
119
|
+
// helper invocation can prevent the service restart.
|
|
120
|
+
return io.run(brew, ['services', 'restart', 'wherdr'], env)
|
|
121
|
+
},
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
// `wherdr start` of the package now in place: the pid file, or the login service.
|
|
115
|
-
function
|
|
116
|
-
|
|
126
|
+
export async function refreshInstalledSkill(plan, io, root = plan.root) {
|
|
127
|
+
try {
|
|
128
|
+
const helper = path.join(root, 'bin', 'lib', 'install-skill.sh')
|
|
129
|
+
if (io.exists(helper)) {
|
|
130
|
+
await io.run('sh', [helper, 'refresh', root], { WHERDR_DIR: plan.dir }, { timeout: 5000 })
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
} catch {} // A skill refresh never prevents an update or rollback.
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function cliStart(plan, io) {
|
|
137
|
+
const refreshed = await refreshInstalledSkill(plan, io)
|
|
138
|
+
return io.run(plan.runtime, [path.join(plan.root, 'bin', 'wherdr.mjs'), 'start'], refreshed ? { WHERDR_SKILL_REFRESHED: '1' } : {})
|
|
117
139
|
}
|
|
118
140
|
|
|
119
141
|
export function driverFor(plan, io) {
|
|
@@ -123,6 +145,26 @@ export function driverFor(plan, io) {
|
|
|
123
145
|
throw new Error(`no one-tap update for ${plan.kind}`)
|
|
124
146
|
}
|
|
125
147
|
|
|
148
|
+
// ------------------------------------------------------- shown on the phone
|
|
149
|
+
// The reason of a failed start as one short sentence: the line that says why,
|
|
150
|
+
// without the command that reported it nor any absolute path (the whole
|
|
151
|
+
// reason is in update.log).
|
|
152
|
+
export function shortReason(reason) {
|
|
153
|
+
const why = String(reason || '')
|
|
154
|
+
.replace(/^.*? exited with \d+: /, '')
|
|
155
|
+
.replace(/^wherdr: /, '')
|
|
156
|
+
.replace(/\s*\((?:[A-Za-z]:)?[\\/][^()]*\)/g, '')
|
|
157
|
+
.replace(/(?:[A-Za-z]:)?[\\/](?:[^\s\\/:]+[\\/])+([^\s\\/:]+)/g, '$1')
|
|
158
|
+
.replace(/[\s.]+$/, '')
|
|
159
|
+
return why.length > 200 ? `${why.slice(0, 199)}…` : why
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function didNotStart(plan, reason) {
|
|
163
|
+
const why = shortReason(reason)
|
|
164
|
+
if (why.startsWith(`wherdr ${plan.to} `)) return why
|
|
165
|
+
return `wherdr ${plan.to} did not start${why ? `: ${why}` : ''}`
|
|
166
|
+
}
|
|
167
|
+
|
|
126
168
|
// ---------------------------------------------------------- state machine
|
|
127
169
|
// installing → restarting → checking → done; a failed install leaves the old
|
|
128
170
|
// server running (failed); a new version that does not start or answer goes
|
|
@@ -152,20 +194,20 @@ export async function runUpdate(plan, io) {
|
|
|
152
194
|
reason = `wherdr ${plan.to} did not answer within ${Math.round(healthMs / 1000)} s`
|
|
153
195
|
} catch (e) { reason = e.message }
|
|
154
196
|
io.log(`${reason}: going back to ${plan.from}`)
|
|
155
|
-
io.state('rolling-back', reason)
|
|
197
|
+
io.state('rolling-back', `${didNotStart(plan, reason)}.`)
|
|
156
198
|
try {
|
|
157
199
|
await io.stopServer()
|
|
158
200
|
await d.rollback()
|
|
159
201
|
await d.start()
|
|
160
202
|
if (await io.healthy(plan.from, healthMs)) {
|
|
161
203
|
io.log(`wherdr ${plan.from} answers again: rolled back`)
|
|
162
|
-
io.state('rolled-back',
|
|
204
|
+
io.state('rolled-back', `${didNotStart(plan, reason)}. Rolled back to ${plan.from}.`)
|
|
163
205
|
return 'rolled-back'
|
|
164
206
|
}
|
|
165
207
|
throw new Error(`wherdr ${plan.from} did not answer either`)
|
|
166
208
|
} catch (e) {
|
|
167
209
|
io.log(`rollback failed: ${e.message}`)
|
|
168
|
-
io.state('failed',
|
|
210
|
+
io.state('failed', `${didNotStart(plan, reason)}. Going back to ${plan.from} failed too: ${shortReason(e.message)}. See update.log in the wherdr folder on the server.`)
|
|
169
211
|
return 'failed'
|
|
170
212
|
}
|
|
171
213
|
}
|
|
@@ -191,22 +233,28 @@ export function realIo(plan, now = Date.now) {
|
|
|
191
233
|
writeFileSync(`${stateFile}.tmp`, `${JSON.stringify(job)}\n`)
|
|
192
234
|
renameSync(`${stateFile}.tmp`, stateFile)
|
|
193
235
|
},
|
|
194
|
-
run(cmd, args, env = {}) {
|
|
236
|
+
run(cmd, args, env = {}, { timeout } = {}) {
|
|
195
237
|
log(`$ ${[cmd, ...args].join(' ')}`)
|
|
196
238
|
const { promise, resolve, reject } = Promise.withResolvers()
|
|
197
|
-
const child = spawn(cmd, args, { cwd: plan.dir, env: { ...process.env, PATH, ...env }, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
239
|
+
const child = spawn(cmd, args, { cwd: plan.dir, env: { ...process.env, PATH, ...env }, detached: Boolean(timeout), stdio: ['ignore', 'pipe', 'pipe'] })
|
|
240
|
+
const timer = timeout ? setTimeout(() => {
|
|
241
|
+
// The shell's own children must not keep copying or hold log pipes open.
|
|
242
|
+
try { process.kill(-child.pid, 'SIGKILL') } catch { child.kill('SIGKILL') }
|
|
243
|
+
reject(new Error(`${path.basename(cmd)} timed out after ${timeout} ms`))
|
|
244
|
+
}, timeout) : undefined
|
|
198
245
|
let out = ''
|
|
199
246
|
const keep = (b) => { out = (out + b).slice(-4000); try { appendFileSync(logFile, b) } catch {} }
|
|
200
247
|
child.stdout.on('data', keep)
|
|
201
248
|
child.stderr.on('data', keep)
|
|
202
|
-
child.on('error', e => reject(new Error(`${path.basename(cmd)}: ${e.message}`)))
|
|
249
|
+
child.on('error', e => { clearTimeout(timer); reject(new Error(`${path.basename(cmd)}: ${e.message}`)) })
|
|
203
250
|
child.on('close', (code) => {
|
|
251
|
+
clearTimeout(timer)
|
|
204
252
|
if (code === 0) return resolve(out)
|
|
205
253
|
// The lines that say what went wrong (`wherdr: …`, `Error: …`), not a stack trace.
|
|
206
254
|
const lines = out.split('\n').map(l => l.trim()).filter(Boolean)
|
|
207
255
|
const why = lines.filter(l => /^(wherdr: |error\b|\w*Error: )/i.test(l)).slice(0, 2)
|
|
208
256
|
const tail = (why.length ? why : lines.slice(-1)).join(' / ')
|
|
209
|
-
reject(new Error(`${path.basename(cmd)} ${args[0]} exited with ${code}${tail ? `: ${tail}` : ''}`))
|
|
257
|
+
reject(new Error(`${path.basename(cmd)} ${path.basename(args[0] ?? '')} exited with ${code}${tail ? `: ${tail}` : ''}`))
|
|
210
258
|
})
|
|
211
259
|
return promise
|
|
212
260
|
},
|
|
@@ -217,6 +265,11 @@ export function realIo(plan, now = Date.now) {
|
|
|
217
265
|
mkdirSync(path.dirname(file), { recursive: true })
|
|
218
266
|
writeFileSync(file, Buffer.from(await res.arrayBuffer()))
|
|
219
267
|
},
|
|
268
|
+
verify(file, checksum) {
|
|
269
|
+
const expected = readFileSync(checksum, 'utf8').trim().split(/\s+/)[0]
|
|
270
|
+
const actual = createHash('sha256').update(readFileSync(file)).digest('hex')
|
|
271
|
+
if (!/^[a-f0-9]{64}$/.test(expected || '') || actual !== expected) throw new Error('Release SHA-256 mismatch')
|
|
272
|
+
},
|
|
220
273
|
// The server being replaced, then whatever `wherdr start` left (pid file).
|
|
221
274
|
async stopServer() {
|
|
222
275
|
const pids = [plan.serverPid]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Check transfer integrity before extraction. Metadata/checksum and archive
|
|
2
|
+
// share an origin: this does not authenticate a publisher or prevent replacement.
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
const [file, checksum, source, version] = process.argv.slice(2)
|
|
6
|
+
let matches = false
|
|
7
|
+
if (source === 'npm') {
|
|
8
|
+
const metadata = JSON.parse(readFileSync(checksum, 'utf8'))
|
|
9
|
+
const integrity = metadata.dist?.integrity
|
|
10
|
+
const actual = createHash('sha512').update(readFileSync(file)).digest('base64')
|
|
11
|
+
matches = metadata.name === 'wherdr' && metadata.version === version && integrity === `sha512-${actual}`
|
|
12
|
+
} else {
|
|
13
|
+
const expected = readFileSync(checksum, 'utf8').trim().split(/\s+/)[0]
|
|
14
|
+
const actual = createHash('sha256').update(readFileSync(file)).digest('hex')
|
|
15
|
+
matches = /^[a-f0-9]{64}$/.test(expected || '') && actual === expected
|
|
16
|
+
}
|
|
17
|
+
if (!matches) {
|
|
18
|
+
console.error('Release checksum mismatch')
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wherdr",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Unofficial mobile web client (PWA) to drive the coding agents running in Herdr",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
".output",
|
|
27
27
|
"!.output/**/*.map",
|
|
28
|
-
"bin"
|
|
28
|
+
"bin",
|
|
29
|
+
"skills/wherdr"
|
|
29
30
|
],
|
|
30
31
|
"engines": {
|
|
31
32
|
"node": ">=22"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wherdr
|
|
3
|
+
description: Conventions for coordinating a herdr-projects project whose TASKS.md is shown and driven by wherdr's Project panel and Board. Use when you coordinate a herdr-projects project (you edit TASKS.md, launch and merge threads), and whenever a message starts with "✓ Tested:", "✓ Testé :", "↳ Problem:", "↳ Problème :", "↳ Question:", "↳ Question :", "✗ Problem:", "✗ Problème :", "? Question:", "? Question :", "↳ Decision:", "↳ Décision :", "↳ Launch", "↳ Lancer", "↳ Move up", "↳ Monter", "↳ Move down", "↳ Descendre", "↳ Queue:", "↳ Mettre en file", "↳ Remove from queue", "↳ Retirer de la file", "↳ Back to backlog", "↳ Remettre au backlog", "↳ Mark blocked", "↳ Marquer bloqué", "↳ Move to To do", "↳ Passer en À faire", "↳ Mark done", "↳ Marquer fait", "↳ Unblock", "↳ Débloquer", "↳ Detail on", "↳ Précision sur", "✓ Reviewed:", "✓ Relu :", "↳ Feedback on", "↳ Retour sur", "↳ Info for", "↳ Info pour" or "↳ Quote".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wherdr Project panel conventions
|
|
7
|
+
|
|
8
|
+
wherdr shows the project's `TASKS.md` as a board on the user's phone and computer, with
|
|
9
|
+
buttons on each task. Each button sends a short message to you, the coordinator. You own
|
|
10
|
+
`TASKS.md`: the panel never edits it, it only reads it and asks you to. wherdr's **Board**
|
|
11
|
+
(every project, thread and agent in Blocked / To do / Ongoing / Finished columns) sends the
|
|
12
|
+
same messages when the user moves a task card to another column, plus the three moves of
|
|
13
|
+
its own listed below.
|
|
14
|
+
|
|
15
|
+
Follow the project's own instructions (`PROJECT.md`) first; this skill fills in what the
|
|
16
|
+
panel expects. Write task lines and badges in the user's language.
|
|
17
|
+
|
|
18
|
+
## TASKS.md format
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
## To test
|
|
22
|
+
- [ ] Swipe between panes on the phone [b:green(ready to test)] [b:gray(t-0042)](t-0042)
|
|
23
|
+
|
|
24
|
+
## In progress
|
|
25
|
+
- [ ] Fix the quota shown for Codex [b:red(bug)] (agent → t-0043)
|
|
26
|
+
|
|
27
|
+
## In queue
|
|
28
|
+
- [ ] Dark mode for the settings
|
|
29
|
+
|
|
30
|
+
## Backlog
|
|
31
|
+
- [ ] Export a conversation as Markdown
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Each `## Title` is a list; each `- [ ] text` (or `- text`) line under it is a task.
|
|
35
|
+
`- [x]` marks it done. `#` headings, `###` subheadings, indented lines (task details) and
|
|
36
|
+
fenced code blocks are ignored.
|
|
37
|
+
- The last `(…)` of a line is the owner (`me`, `agent`, a name, `agent → t-NNNN`): read,
|
|
38
|
+
never shown.
|
|
39
|
+
- Lists are shown in a fixed order (the order below), whatever their order in the file.
|
|
40
|
+
Unknown titles are shown after Backlog, without buttons. Only lists present in the file
|
|
41
|
+
are shown (plus In progress when threads are open, and Done). Add or remove a list only
|
|
42
|
+
when the user asks.
|
|
43
|
+
|
|
44
|
+
## Lists the panel recognizes
|
|
45
|
+
|
|
46
|
+
Titles are matched case- and accent-insensitively; any of these names works.
|
|
47
|
+
|
|
48
|
+
| List | Accepted titles | Meaning | Buttons |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| To test | À tester, To test, Testing, To verify, À vérifier, Test | Deployed, ready for the user to check. | Confirm, Problem, Question |
|
|
51
|
+
| To decide | À décider, To decide, Decision(s), Decide, Question(s) | Questions only the user can settle. | Question, Answer |
|
|
52
|
+
| To review | À relire, Relire, Relecture, To review, Review(s), Code review, PR(s), Pull request(s), À valider, To validate | Pull requests for the user to review; put the PR link in a badge or in the text. | Reviewed, Comment |
|
|
53
|
+
| In progress | En cours, In progress, Doing, Ongoing, WIP, Active | Only what a thread is really doing now. Open threads are shown here live. | Add info (on a thread) |
|
|
54
|
+
| In queue | En file, En file d'attente, File d'attente, File, In queue, Queue, Queued, Up next | Decided work: launch the first task as soon as a thread slot frees, in list order. | Up, Down, Launch now, Clarify, Remove from queue |
|
|
55
|
+
| Blocked | Bloqué(e)(s), Blocked, On hold, En attente, Waiting, Stuck | Waiting for something external. Optional reason: `— blocked by: <reason>` (`— bloqué par : …`). | Unblock, Clarify |
|
|
56
|
+
| To do | À faire, Todo, To do, To dos, Todos, Next, Prochainement, Soon | Soon, in the user's priority order. Never launched without a request. | Up, Down, Queue, Launch now, Clarify, Back to backlog |
|
|
57
|
+
| Backlog | Backlog, Later, Plus tard, Idées, Ideas, Someday, Un jour | Everything else. Never launched without a request. | Launch, Clarify |
|
|
58
|
+
| Done | Fait(s), Done, Terminé(e)(s), Finished, Complete(d) | Filled by the panel with resolved threads; you may leave it out. | — |
|
|
59
|
+
|
|
60
|
+
Rules:
|
|
61
|
+
|
|
62
|
+
- **To test** holds only what is deployed and ready. Work being fixed goes back to In
|
|
63
|
+
progress; waiting work goes to In queue.
|
|
64
|
+
- **No duplicates across lists.** When the user asks for a change to a feature that is in
|
|
65
|
+
To test, remove its To test line; one line comes back to To test once the change is
|
|
66
|
+
deployed (feature and change tested together).
|
|
67
|
+
- **In queue** contract: whenever a thread slot frees (a thread is resolved), launch the
|
|
68
|
+
first In queue task and move it to In progress, as long as the project and the machine
|
|
69
|
+
have a free slot. The In queue header shows the thread slots in use
|
|
70
|
+
(`max_parallel_threads` of `PROJECT.md`).
|
|
71
|
+
|
|
72
|
+
## Global thread limit per machine
|
|
73
|
+
|
|
74
|
+
The user can set a maximum of threads per machine, across all projects, in wherdr
|
|
75
|
+
(Settings › Plugins › herdr-projects). wherdr then writes `.wherdr-limits.json` in the
|
|
76
|
+
herdr-projects folder (the parent of the project folder):
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "updated": "…", "this": "Server", "machines": { "Server": { "max": 2, "open": 2, "free": 0, "threads": ["shop/t-0012"] } } }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Before starting a thread, read `machines[this].free` (or `machines["<machine>"]` for a
|
|
83
|
+
thread on another machine; `max: null` means no global limit). When `free` is `0`, wait,
|
|
84
|
+
even if the project still has slots. File missing or out of date: count the open threads of
|
|
85
|
+
every project with `herdr-projects overview`. Go beyond a limit only when the user says so.
|
|
86
|
+
|
|
87
|
+
## `(agent → t-NNNN)`
|
|
88
|
+
|
|
89
|
+
Write an In progress task handled by an open thread with the owner `(agent → t-NNNN)`
|
|
90
|
+
(the arrow may also be `->`). The panel hides that line while the thread's own live card is
|
|
91
|
+
shown, so the task does not appear twice. Write the thread ID exactly (`t-` and at least
|
|
92
|
+
four digits).
|
|
93
|
+
|
|
94
|
+
## Badges
|
|
95
|
+
|
|
96
|
+
The only decoration the panel draws. Anywhere on a task line:
|
|
97
|
+
|
|
98
|
+
- `[b:color(text)]`: a badge. `[b:(text)]` is neutral.
|
|
99
|
+
- `[b:color(text)](target)`: a clickable badge. Target: an `https://` (or `http://`) URL, or
|
|
100
|
+
a thread ID (`t-0042`, opens that thread's tab). Anything else is not clickable.
|
|
101
|
+
- Colors: `red`, `orange`, `amber`, `green`, `teal`, `blue`, `violet`, `pink`, `gray`
|
|
102
|
+
(`grey`), a hex color (`#e5484d`, `#fff`), or nothing (neutral).
|
|
103
|
+
- Text: short, at most 24 characters. It is removed from the task text (the text the
|
|
104
|
+
panel sends back to you).
|
|
105
|
+
|
|
106
|
+
Use them sparingly, when they help the user: two or three per line at most. Defaults
|
|
107
|
+
unless the user prefers otherwise:
|
|
108
|
+
|
|
109
|
+
- `[b:gray(t-0042)](t-0042)`: the thread a line comes from.
|
|
110
|
+
- `[b:green(ready to test)]`: every To test line.
|
|
111
|
+
- `[b:amber(fixing)]` (with the thread badge) on In progress work being fixed;
|
|
112
|
+
`[b:gray(queued)]` on waiting work.
|
|
113
|
+
- `[b:red(bug)]`: a fix. `[b:blue(iPhone)]`: to test on the phone.
|
|
114
|
+
- A PR: `[b:violet(PR #12)](https://github.com/<owner>/<repo>/pull/12)`.
|
|
115
|
+
|
|
116
|
+
A bare `http://` or `https://` URL in the task text also becomes a plain clickable
|
|
117
|
+
link (for example `Review https://github.com/example/project/pull/12`). Use this
|
|
118
|
+
when the URL should remain visible; use a linked badge for a compact label.
|
|
119
|
+
|
|
120
|
+
## Messages from the panel
|
|
121
|
+
|
|
122
|
+
`<task>` is the task text as the panel shows it: the line without its checkbox, badges and
|
|
123
|
+
owner. Find the line in `TASKS.md` by that text. The English or French form depends on the
|
|
124
|
+
app language; treat them the same.
|
|
125
|
+
|
|
126
|
+
Messages marked *sent* arrive as is. Messages marked *draft* are put in the user's input
|
|
127
|
+
field, completed by the user, then sent. A draft starts with `↳` and ends with ` —`: it is a
|
|
128
|
+
**reference**, the task on one line, the user's text on the lines below it, up to the next
|
|
129
|
+
reference. (Earlier versions wrote a problem and a question on one line, `✗ Problem: <task> —
|
|
130
|
+
<details>` and `? Question: <task> — <question>`: treat them the same.)
|
|
131
|
+
|
|
132
|
+
The user quotes your own messages with the same kind of line, by the time shown on the message:
|
|
133
|
+
`↳ Quote message 11:37 : <start of the message> —` (the whole message),
|
|
134
|
+
`↳ Quote point 11:37 : <question, point or passage> —`,
|
|
135
|
+
`↳ Quote decision 11:37 #3 : <question of row 3> —` (a row of a table of numbered decisions) and
|
|
136
|
+
`↳ Quote decisions 11:37 #1-4 : <questions> —` (the whole table, usually followed by `OK to all`;
|
|
137
|
+
`#1,4` when the rows are not consecutive).
|
|
138
|
+
Read the lines under each one as the answer to that message and that row, even when you have
|
|
139
|
+
written other messages since. Long excerpts are cut with `…`. A short code may follow the time
|
|
140
|
+
or the row, as in `11:37 #3 ~k3f`: wherdr uses it to tell apart two of your messages written in
|
|
141
|
+
the same minute, or two tables of one message. You have nothing to do with it.
|
|
142
|
+
`↳ Info for` names a thread (`t-NNNN`) instead of a task; its text may span several lines.
|
|
143
|
+
|
|
144
|
+
| Message (EN / FR) | From | What to do |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `✓ Tested: <task>` / `✓ Testé : <task>` (sent) | To test › Confirm | The user validated it: remove the line from To test. |
|
|
147
|
+
| `↳ Problem: <task> —` / `↳ Problème : <task> —`, then the details below (draft) | To test › Problem | A bug report: remove the line from To test, fix it (or launch a thread) and track it in In progress; it comes back to To test once the fix is deployed. |
|
|
148
|
+
| `↳ Question: <task> —` / `↳ Question : <task> —`, then the question below (draft) | To test, To decide › Question | Answer in detail: what to test and how (where to tap, expected result), from the thread's report. Do not change `TASKS.md`. |
|
|
149
|
+
| `↳ Decision: <task> —` / `↳ Décision : <task> —`, then the answer below (draft) | To decide › Answer | Apply the decision, remove the line from To decide, and record it where the project keeps durable decisions. |
|
|
150
|
+
| `✓ Reviewed: <task>` / `✓ Relu : <task>` (sent) | To review › Reviewed | The user reviewed the PR: carry on (merge, deploy as the project says) and remove the line from To review. |
|
|
151
|
+
| `↳ Feedback on <task> —` / `↳ Retour sur <task> —`, then the comments below (draft) | To review › Comment | Review feedback on that PR: apply it or send it to the thread. |
|
|
152
|
+
| `↳ Move up: <task>` / `↳ Monter : <task>` (sent) | To do, In queue | Move the line one place up in its list. May arrive several times in a row. |
|
|
153
|
+
| `↳ Move down: <task>` / `↳ Descendre : <task>` (sent) | To do, In queue | Move the line one place down in its list. |
|
|
154
|
+
| `↳ Queue: <task>` / `↳ Mettre en file : <task>` (sent) | To do › Queue | Move the line from To do to the end of In queue; launch it if a slot is free and it is first. |
|
|
155
|
+
| `↳ Launch now: <task>` / `↳ Lancer maintenant : <task>` (sent) | To do, In queue › Launch now | Launch a thread now if the project and the machine have a free slot (beyond the limits only if the user says so); otherwise put the line first in In queue. |
|
|
156
|
+
| `↳ Remove from queue: <task>` / `↳ Retirer de la file : <task>` (sent) | In queue | Move the line back to the top of To do. |
|
|
157
|
+
| `↳ Back to backlog: <task>` / `↳ Remettre au backlog : <task>` (sent) | To do | Move the line to Backlog. |
|
|
158
|
+
| `↳ Launch: <task>` / `↳ Lancer : <task>` (sent) | Backlog › Launch | Start the task: a thread if a slot is free, otherwise first in In queue. |
|
|
159
|
+
| `↳ Unblock: <task>` / `↳ Débloquer : <task>` (sent) | Blocked › Unblock | Relaunch the task, or ask the user exactly what is still missing. |
|
|
160
|
+
| `↳ Mark blocked: <task>` / `↳ Marquer bloqué : <task>` (sent) | Board: a task moved to Blocked | Move the line to Blocked (add that list if it is missing); ask the user what it waits for if you do not know. |
|
|
161
|
+
| `↳ Move to To do: <task>` / `↳ Passer en À faire : <task>` (sent) | Board: a task moved to To do (from Blocked, In progress or Done) | Move the line to the end of To do (add that list if it is missing), unchecked. Do not launch it. |
|
|
162
|
+
| `↳ Mark done: <task>` / `↳ Marquer fait : <task>` (sent) | Board: a task moved to Finished | The task is finished or no longer needed: move the line, checked, to Done when the project has that list, otherwise remove it. |
|
|
163
|
+
| `↳ Detail on <task> —` / `↳ Précision sur <task> —`, then the details below (draft) | Clarify (Backlog, To do, In queue, Blocked) | Add the details to the task (indented lines under it, or the thread's brief) and confirm. Do not launch it. |
|
|
164
|
+
| `↳ Info for t-NNNN: <text>` / `↳ Info pour t-NNNN : <text>` (sent) | In progress › Add info (a thread's card, or the task of a thread) | Pass `<text>` on to that thread as is, every line of it, without rewording (`herdr-projects thread prompt <project> t-NNNN --text-file <file>`, `-` reads standard input), then confirm to the user in one line. Do not change `TASKS.md`. If the thread is resolved or unknown, say so instead. |
|
|
165
|
+
|
|
166
|
+
Never launch a task from To do or Backlog without one of these requests.
|
|
167
|
+
|
|
168
|
+
## After a deployment
|
|
169
|
+
|
|
170
|
+
For each change the user can now see, add one To test line written from the user's point
|
|
171
|
+
of view: what to open and what should happen, not the technical cause ("Swipe between
|
|
172
|
+
panes on the phone", not "Fix pointer events in PaneSwiper"). Add
|
|
173
|
+
`[b:green(ready to test)]` and the thread badge. Remove the matching In progress line.
|
|
174
|
+
|
|
175
|
+
Name threads and tasks the same way: what the user sees.
|
|
176
|
+
|
|
177
|
+
## Questions left unanswered
|
|
178
|
+
|
|
179
|
+
A question you asked the user that is still open goes to To decide, so the user can answer
|
|
180
|
+
it from the panel. Remove the line as soon as it is answered.
|