@jenga-ai/agent 1.0.1 → 1.1.1
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/README.md +10 -7
- package/agents/developer.md +82 -2
- package/agents/scrum-master.md +215 -21
- package/agents/tester.md +90 -8
- package/hooks/on_session_end.sh +171 -20
- package/mcp/router/embedder.js +1 -1
- package/mcp/training_runner/index.js +239 -0
- package/mcp/training_runner/package-lock.json +1065 -0
- package/mcp/training_runner/package.json +15 -0
- package/package.json +14 -16
- package/scripts/check-permission-level.sh +107 -0
- package/scripts/check-publicignore-match.sh +122 -0
- package/scripts/check-worktree-liveness.sh +193 -0
- package/scripts/generate-rapport-manifest.sh +43 -0
- package/scripts/idea_manager.sh +47 -0
- package/scripts/install-worktree-commit-guard.sh +134 -0
- package/scripts/jenga-permission-level-switch.sh +109 -0
- package/scripts/smoke-harness.sh +139 -0
- package/scripts/validate-board.sh +62 -0
- package/scripts/with-lock.sh +158 -0
- package/scripts/worktree-remove-guard.sh +204 -0
- package/skills/clearify/SKILL.md +52 -0
- package/skills/close-story/SKILL.md +203 -0
- package/skills/close-story/scripts/check-story-closeable.sh +195 -0
- package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
- package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
- package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
- package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
- package/skills/commit/SKILL.md +30 -3
- package/skills/distribute/CONFIG_SCHEMA.md +148 -0
- package/skills/distribute/SKILL.md +173 -0
- package/skills/distribute/scripts/check-version.sh +74 -0
- package/skills/distribute/scripts/commit-version-bump.sh +108 -0
- package/skills/distribute/scripts/distribute-changes.sh +381 -0
- package/skills/do/SKILL.md +352 -1
- package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
- package/skills/doc/assets/path-objectives.yaml +13 -0
- package/skills/doc-sync/SKILL.md +16 -0
- package/skills/doc-sync/assets/doc_targets.md +11 -0
- package/skills/idea/SKILL.md +56 -0
- package/skills/idea/assets/idea_handoff_template.md +26 -0
- package/skills/idea/assets/idea_template.md +3 -0
- package/skills/init/SKILL.md +101 -7
- package/skills/init/assets/directory_structure.txt +1 -0
- package/skills/init/assets/strategy_stub_template.md +38 -0
- package/skills/init/assets/workflow_template.json +1 -1
- package/skills/init/scripts/apply-project-visibility.sh +176 -0
- package/skills/init/scripts/detect-existing-codebase.sh +166 -0
- package/skills/init/scripts/init.sh +35 -1
- package/skills/jenga/SKILL.md +206 -14
- package/skills/jenga/scripts/board-scan.sh +238 -0
- package/skills/jenga/scripts/cascade-resolve.sh +297 -0
- package/skills/jenga/scripts/render-confirmation.sh +679 -0
- package/skills/jenga/scripts/render-picker.sh +439 -0
- package/skills/jenga/scripts/resolve-id.sh +367 -0
- package/skills/jenga-permission-level/SKILL.md +81 -0
- package/skills/proceed/SKILL.md +1 -1
- package/skills/publish/SKILL.md +8 -5
- package/skills/publish/assets/ci-contract.md +2 -2
- package/skills/publish/assets/ownership-matrix.md +1 -1
- package/skills/publish/scripts/finalize_changelog.sh +115 -0
- package/skills/publish/scripts/generate_release_notes.sh +475 -28
- package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
- package/skills/publish/scripts/publish_deploy.sh +38 -8
- package/skills/publish/scripts/run_gates.sh +2 -2
- package/skills/reconcile/SKILL.md +117 -5
- package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
- package/skills/spinoff/SKILL.md +12 -7
- package/skills/todo/SKILL.md +2 -0
- package/skills/uncharted/SKILL.md +711 -0
- package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
- package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
- package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
- package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
- package/skills/uncharted/scripts/detect-tests.sh +553 -0
- package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
- package/skills/uncharted/scripts/enumerate-target.sh +470 -0
- package/skills/uncharted/scripts/import-source.sh +517 -0
- package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
- package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
- package/skills/uncharted/scripts/run-engine.sh +655 -0
- package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
- package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
- package/skills/wtf/SKILL.md +20 -0
- package/templates/CHANGELOG_TEMPLATE.md +13 -0
- package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
- package/templates/permission-levels/README.md +73 -0
- package/templates/permission-levels/level-1-locked.json +71 -0
- package/templates/permission-levels/level-2-guarded.json +64 -0
- package/templates/permission-levels/level-3-standard.json +62 -0
- package/templates/permission-levels/level-4-elevated.json +60 -0
- package/templates/permission-levels/level-5-unrestricted.json +58 -0
- package/skills/convert/SKILL.md +0 -124
- package/skills/convert/convert_cli.py +0 -235
- package/skills/convert/tests/sample.csv +0 -4
- package/skills/convert/tests/sample.json +0 -5
- package/skills/convert/tests/sample.jsonl +0 -3
- package/skills/convert/tests/sample.yaml +0 -18
- package/skills/convert/tests/sample_obj.csv +0 -2
- package/skills/convert/tests/sample_obj.json +0 -9
- package/skills/mirror-public/SKILL.md +0 -237
- package/skills/mirror-public/assets/config.json +0 -5
- package/skills/mirror-public/scripts/mirror.sh +0 -374
- package/skills/self-sync/SKILL.md +0 -73
- package/skills/self-sync/scripts/run.js +0 -136
- package/skills/train/SKILL.md +0 -116
- package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
- package/skills/train/assets/dashboard-templates/nlp.html +0 -102
- package/skills/train/assets/dashboard-templates/transformers.html +0 -98
- package/skills/train/assets/results-parsers/__init__.py +0 -9
- package/skills/train/assets/results-parsers/classifiers.py +0 -84
- package/skills/train/assets/results-parsers/nlp.py +0 -88
- package/skills/train/assets/results-parsers/reporter.py +0 -154
- package/skills/train/assets/results-parsers/transformers.py +0 -120
- package/skills/train/train_cli.py +0 -786
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/worktree-remove-guard.sh — hard-block-by-default liveness
|
|
3
|
+
# precondition for `git worktree remove`, wired into the `WorktreeRemove`
|
|
4
|
+
# hook in (root, canonical) settings.json.
|
|
5
|
+
#
|
|
6
|
+
# Why this exists: see project/board/epics/E37_inter-agent-completion-signaling.md
|
|
7
|
+
# and project/board/stories/E37_S03_worktree-remove-liveness-precondition.md.
|
|
8
|
+
# `git worktree remove --force` previously ran unconditionally as soon as
|
|
9
|
+
# the WorktreeRemove hook fired, with no precondition of any kind — this is
|
|
10
|
+
# how a 2026-08-25 incident's orphaned shell polling loops got their cwd
|
|
11
|
+
# deleted out from under them. This script now runs
|
|
12
|
+
# scripts/check-worktree-liveness.sh (E37_S03_T01) first, and only proceeds
|
|
13
|
+
# to `git worktree remove --force` if that check reports the path clear (or
|
|
14
|
+
# an explicit override is set).
|
|
15
|
+
#
|
|
16
|
+
# Invocation modes:
|
|
17
|
+
#
|
|
18
|
+
# 1. Hook mode (no positional worktree-path argument): reads a JSON
|
|
19
|
+
# payload from stdin and extracts `.worktree_path` (required) and an
|
|
20
|
+
# optional `.force_ignore_liveness` boolean field. This is how the
|
|
21
|
+
# WorktreeRemove hook actually invokes this script — matching the
|
|
22
|
+
# pre-existing `jq -r '.worktree_path'` convention this script
|
|
23
|
+
# replaces. Note: the native harness's WorktreeRemove hook JSON
|
|
24
|
+
# schema is outside this repo's control (its `ExitWorktree` tool
|
|
25
|
+
# exposes only `action`/`discard_changes`, no pass-through custom
|
|
26
|
+
# field), so `.force_ignore_liveness` on the payload is supported for
|
|
27
|
+
# any caller that *does* control the JSON directly (e.g. a script
|
|
28
|
+
# invoking this guard outside the native tool), but is not the primary
|
|
29
|
+
# override mechanism for the native hook path — see the environment
|
|
30
|
+
# variable below for that.
|
|
31
|
+
#
|
|
32
|
+
# 2. CLI mode (direct/manual invocation — used for this script's own
|
|
33
|
+
# testing and any other scripted use outside the hook):
|
|
34
|
+
# scripts/worktree-remove-guard.sh [--force-ignore-liveness] <worktree-path>
|
|
35
|
+
#
|
|
36
|
+
# Override (works in both modes): set the environment variable
|
|
37
|
+
# WORKTREE_REMOVE_FORCE_IGNORE_LIVENESS=1
|
|
38
|
+
# immediately before invoking this script (directly, or before whatever
|
|
39
|
+
# triggers the WorktreeRemove hook). Accepted truthy values: 1, true, TRUE,
|
|
40
|
+
# True, yes, YES. This is the one lever guaranteed to work regardless of
|
|
41
|
+
# invocation mode or what JSON the calling harness does or doesn't pass
|
|
42
|
+
# through. It must be set explicitly on each invocation — it is never read
|
|
43
|
+
# from a file, never cached, and never a default; every invocation re-reads
|
|
44
|
+
# the environment fresh.
|
|
45
|
+
#
|
|
46
|
+
# Decision logic:
|
|
47
|
+
# - If an override is set (CLI flag, JSON field, or env var): skip the
|
|
48
|
+
# liveness check, print a visible bypass notice to stderr, and run
|
|
49
|
+
# `git worktree remove --force <path>`.
|
|
50
|
+
# - Otherwise, run scripts/check-worktree-liveness.sh <path>. ANY non-zero
|
|
51
|
+
# exit blocks removal by default — not just exit 1 ("live process
|
|
52
|
+
# found"), but also exit 2/3/4 (usage error / invalid path / liveness
|
|
53
|
+
# indeterminate), since none of those represent "the path is
|
|
54
|
+
# affirmatively clear," consistent with that script's own fail-closed
|
|
55
|
+
# design. `git worktree remove` is never invoked in this branch. The
|
|
56
|
+
# liveness check's own PID/command output (or error message) is
|
|
57
|
+
# surfaced to the caller, plus a note on how to override.
|
|
58
|
+
# - On a clear (exit 0) result: proceed to `git worktree remove --force
|
|
59
|
+
# <path>` exactly as the old unconditional one-liner did, with no extra
|
|
60
|
+
# output — the "clear worktree" case must behave exactly as before,
|
|
61
|
+
# with no added friction.
|
|
62
|
+
#
|
|
63
|
+
# Exit codes:
|
|
64
|
+
# * whatever `git worktree remove --force <path>` itself exits with, if
|
|
65
|
+
# removal proceeded (clear path, or override set)
|
|
66
|
+
# 1 blocked — liveness check reported non-zero and no override was set;
|
|
67
|
+
# git worktree remove was NOT run
|
|
68
|
+
# 2 usage error — no worktree path resolvable from CLI args or stdin
|
|
69
|
+
# JSON, or an unrecognized flag
|
|
70
|
+
|
|
71
|
+
set -u
|
|
72
|
+
|
|
73
|
+
SCRIPT_NAME="worktree-remove-guard.sh"
|
|
74
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
|
|
75
|
+
LIVENESS_SCRIPT="$SCRIPT_DIR/check-worktree-liveness.sh"
|
|
76
|
+
|
|
77
|
+
# Resolve a safe anchor directory to relocate this process's own cwd into
|
|
78
|
+
# further below (see the "Caller-cwd self-detection hardening" block after
|
|
79
|
+
# argument parsing) — the main repository's working-tree root, derived from
|
|
80
|
+
# the shared git common dir so it resolves correctly regardless of which
|
|
81
|
+
# worktree this script's own cwd currently happens to be in. Must be
|
|
82
|
+
# computed now, from the ORIGINAL cwd, since `git rev-parse` depends on
|
|
83
|
+
# being inside a git working tree.
|
|
84
|
+
GIT_COMMON_DIR_RAW="$(git rev-parse --git-common-dir 2>/dev/null)"
|
|
85
|
+
SAFE_ANCHOR=""
|
|
86
|
+
if [ -n "$GIT_COMMON_DIR_RAW" ]; then
|
|
87
|
+
SAFE_ANCHOR="$(cd "$(dirname "$GIT_COMMON_DIR_RAW")" >/dev/null 2>&1 && pwd -P)"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
FORCE_OVERRIDE=0
|
|
91
|
+
WORKTREE_PATH=""
|
|
92
|
+
|
|
93
|
+
# --- Parse CLI-style args, if any. ---
|
|
94
|
+
while [ "$#" -gt 0 ]; do
|
|
95
|
+
case "$1" in
|
|
96
|
+
--force-ignore-liveness)
|
|
97
|
+
FORCE_OVERRIDE=1
|
|
98
|
+
shift
|
|
99
|
+
;;
|
|
100
|
+
--)
|
|
101
|
+
shift
|
|
102
|
+
break
|
|
103
|
+
;;
|
|
104
|
+
-*)
|
|
105
|
+
echo "$SCRIPT_NAME: unknown flag '$1'" >&2
|
|
106
|
+
exit 2
|
|
107
|
+
;;
|
|
108
|
+
*)
|
|
109
|
+
break
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
done
|
|
113
|
+
if [ "$#" -ge 1 ]; then
|
|
114
|
+
WORKTREE_PATH="$1"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# --- Hook mode: no positional worktree path given, so read the JSON
|
|
118
|
+
# payload the WorktreeRemove hook receives on stdin. ---
|
|
119
|
+
if [ -z "$WORKTREE_PATH" ]; then
|
|
120
|
+
if [ -t 0 ]; then
|
|
121
|
+
echo "$SCRIPT_NAME: no worktree path given (expected a positional argument, or JSON on stdin with a 'worktree_path' field)" >&2
|
|
122
|
+
exit 2
|
|
123
|
+
fi
|
|
124
|
+
PAYLOAD="$(cat)"
|
|
125
|
+
WORKTREE_PATH="$(printf '%s' "$PAYLOAD" | jq -r '.worktree_path // empty' 2>/dev/null)"
|
|
126
|
+
if [ "$FORCE_OVERRIDE" -eq 0 ]; then
|
|
127
|
+
PAYLOAD_OVERRIDE="$(printf '%s' "$PAYLOAD" | jq -r 'if .force_ignore_liveness == true then "true" else "false" end' 2>/dev/null)"
|
|
128
|
+
[ "$PAYLOAD_OVERRIDE" = "true" ] && FORCE_OVERRIDE=1
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [ -z "$WORKTREE_PATH" ]; then
|
|
133
|
+
echo "$SCRIPT_NAME: no worktree path provided (expected a positional argument, or a 'worktree_path' field on stdin JSON)" >&2
|
|
134
|
+
exit 2
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Resolve WORKTREE_PATH to an absolute, symlink-resolved path NOW, while
|
|
138
|
+
# still in the original cwd — we're about to relocate this process's own
|
|
139
|
+
# cwd away (hardening below), and a relative path would no longer resolve
|
|
140
|
+
# correctly against the caller's original cwd afterward. (In practice the
|
|
141
|
+
# hook always supplies an absolute path — see WorktreeCreate's own
|
|
142
|
+
# `DIR="$JENGA_PROJECT_DIR/.claude/worktrees/$NAME"` construction — this
|
|
143
|
+
# also makes relative paths work correctly for CLI-mode/manual use.) If
|
|
144
|
+
# resolution fails (path does not exist), fall through with the original,
|
|
145
|
+
# unresolved string; check-worktree-liveness.sh and/or `git worktree
|
|
146
|
+
# remove` will report the appropriate error themselves.
|
|
147
|
+
WORKTREE_PATH_RESOLVED="$(cd "$WORKTREE_PATH" 2>/dev/null && pwd -P)"
|
|
148
|
+
[ -n "$WORKTREE_PATH_RESOLVED" ] && WORKTREE_PATH="$WORKTREE_PATH_RESOLVED"
|
|
149
|
+
|
|
150
|
+
# --- Environment override — checked last so it can force the bypass
|
|
151
|
+
# regardless of how the path was supplied. ---
|
|
152
|
+
case "${WORKTREE_REMOVE_FORCE_IGNORE_LIVENESS:-}" in
|
|
153
|
+
1|true|TRUE|True|yes|YES)
|
|
154
|
+
FORCE_OVERRIDE=1
|
|
155
|
+
;;
|
|
156
|
+
esac
|
|
157
|
+
|
|
158
|
+
# Caller-cwd self-detection hardening (this script's own layer of it — see
|
|
159
|
+
# the matching comment block in check-worktree-liveness.sh for the
|
|
160
|
+
# underlying check script's own layer, which protects only its own spawned
|
|
161
|
+
# subprocesses). THIS script is what actually invokes that check below —
|
|
162
|
+
# if this script's own process was itself launched with a cwd already
|
|
163
|
+
# inside the worktree being removed (a real, expected scenario: the
|
|
164
|
+
# WorktreeRemove hook may fire from a session whose cwd is inside the
|
|
165
|
+
# worktree about to be removed, right before cleanup), this script's own
|
|
166
|
+
# PID would itself be "a live process rooted in" that path at the moment
|
|
167
|
+
# the check runs — check-worktree-liveness.sh's $$-exclusion only ever
|
|
168
|
+
# covers its own PID, not its caller's. Relocating to the main repo root
|
|
169
|
+
# (resolved above, before WORKTREE_PATH is used any further) — rather than
|
|
170
|
+
# to `/` — means both the liveness check AND the eventual `git worktree
|
|
171
|
+
# remove` below keep working normally (git needs to run from inside some
|
|
172
|
+
# working tree), while still guaranteeing neither this script's own process
|
|
173
|
+
# nor the check script it spawns as a child can be cwd'd inside the target,
|
|
174
|
+
# regardless of what cwd the original caller had. Falls back to `/` only if
|
|
175
|
+
# the repo-root anchor could not be resolved (a degenerate case; `git
|
|
176
|
+
# worktree remove` itself will then fail with its own clear error).
|
|
177
|
+
# (Residual, out-of-our-control limitation: if some ancestor further up the
|
|
178
|
+
# process chain — e.g. the harness's own hook-runner shell, if it stays
|
|
179
|
+
# resident rather than exec'ing straight into this script — independently
|
|
180
|
+
# keeps its own cwd inside the target and stays alive while waiting on this
|
|
181
|
+
# script, that ancestor is a separate process this script cannot
|
|
182
|
+
# retroactively relocate; the courtesy $$-style exclusion pattern used
|
|
183
|
+
# throughout these two scripts only ever covers processes each script
|
|
184
|
+
# itself controls.)
|
|
185
|
+
cd "${SAFE_ANCHOR:-/}" 2>/dev/null || true
|
|
186
|
+
|
|
187
|
+
if [ "$FORCE_OVERRIDE" -eq 1 ]; then
|
|
188
|
+
echo "$SCRIPT_NAME: liveness check explicitly bypassed (--force-ignore-liveness / WORKTREE_REMOVE_FORCE_IGNORE_LIVENESS) for '$WORKTREE_PATH' — proceeding to remove without checking for live processes." >&2
|
|
189
|
+
git worktree remove --force "$WORKTREE_PATH"
|
|
190
|
+
exit $?
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
LIVENESS_OUTPUT="$("$LIVENESS_SCRIPT" "$WORKTREE_PATH" 2>&1)"
|
|
194
|
+
LIVENESS_EXIT=$?
|
|
195
|
+
|
|
196
|
+
if [ "$LIVENESS_EXIT" -eq 0 ]; then
|
|
197
|
+
git worktree remove --force "$WORKTREE_PATH"
|
|
198
|
+
exit $?
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
echo "$SCRIPT_NAME: BLOCKED — not removing '$WORKTREE_PATH' (liveness check exited $LIVENESS_EXIT):" >&2
|
|
202
|
+
[ -n "$LIVENESS_OUTPUT" ] && echo "$LIVENESS_OUTPUT" >&2
|
|
203
|
+
echo "$SCRIPT_NAME: git worktree remove was NOT run. If this is expected (e.g. a known, accepted background process), re-run with an explicit override: pass --force-ignore-liveness (CLI mode), a true 'force_ignore_liveness' field on the input JSON (hook mode), or set WORKTREE_REMOVE_FORCE_IGNORE_LIVENESS=1 in the environment (either mode)." >&2
|
|
204
|
+
exit 1
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearify
|
|
3
|
+
description: Clarifies ambiguous, dense, or under-specified prompts and conversation on request — inspects an attached prompt or falls back to the current conversation and surfaces plain-language clarifications with examples.
|
|
4
|
+
keywords:
|
|
5
|
+
- clarify this
|
|
6
|
+
- clarify
|
|
7
|
+
- what do you mean
|
|
8
|
+
- I don't understand
|
|
9
|
+
- wtf
|
|
10
|
+
- explain that again
|
|
11
|
+
- can you simplify
|
|
12
|
+
examples:
|
|
13
|
+
- "clarify this"
|
|
14
|
+
- "clearify the last message"
|
|
15
|
+
- "wtf does this mean"
|
|
16
|
+
- "I don't understand what you're asking me to do here"
|
|
17
|
+
alias: wtf
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Clearify — Ambiguity Clarification
|
|
21
|
+
|
|
22
|
+
> **Note on the `alias: wtf` frontmatter field:** this repo has no runtime mechanism that reads an `alias` key to route slash commands — no existing `SKILL.md` implements one, and `project/configs/workflow.json` has no alias registry. The field here documents the intended relationship only. `/wtf` is made invocable as a working alias by the companion skill folder at `skills/wtf/SKILL.md`, which delegates to these same instructions.
|
|
23
|
+
|
|
24
|
+
## Instructions
|
|
25
|
+
|
|
26
|
+
1. **Determine the target content.**
|
|
27
|
+
- If the user attached a prompt, file, or pasted block of text with this invocation, treat that as the target content.
|
|
28
|
+
- Otherwise, fall back to the most recent user message, plus any surrounding conversation context needed to make sense of it (e.g. the message it's replying to, an earlier instruction it references).
|
|
29
|
+
- Note: this is the only fallback distinction that matters — do not ask the user which content to clarify; infer it from what's available.
|
|
30
|
+
|
|
31
|
+
2. **Identify ambiguous or dense formulations** in the target content. Look for:
|
|
32
|
+
- Jargon or domain-specific terms used without definition
|
|
33
|
+
- Unclear pronouns or references ("it", "that", "this one" — where the referent isn't obvious)
|
|
34
|
+
- Compound asks (multiple distinct requests bundled into one sentence)
|
|
35
|
+
- Unstated assumptions (the request depends on context the reader doesn't have)
|
|
36
|
+
- Vague quantifiers or qualifiers ("soon", "a bit", "some of them") where precision matters
|
|
37
|
+
- Overloaded or dense sentences that pack too much meaning into too little structure
|
|
38
|
+
|
|
39
|
+
3. **For each ambiguous item found, output a structured block** with:
|
|
40
|
+
- **Plain-language clarification** — what it most likely means, stated simply and directly
|
|
41
|
+
- **Simplified restatement** — the original phrasing rewritten in plain terms
|
|
42
|
+
- **Additional context** — relevant background the user may be missing (why this term/reference matters, what it typically implies)
|
|
43
|
+
- **Example(s)** — a concrete worked example, included only where it would actually help clarify the specific ambiguity — skip this sub-section for an item if an example wouldn't add value, rather than manufacturing a weak one
|
|
44
|
+
|
|
45
|
+
4. **Format the output so it's scannable.** Use a header or bold label per ambiguous item (e.g. `### 1. "the usual setup"` or `**1. "the usual setup"**`) — never collapse multiple ambiguous items into a single unstructured paragraph. If there are multiple items, number them in the order they appear in the source content.
|
|
46
|
+
|
|
47
|
+
5. **Handle the zero-ambiguity case explicitly.** If nothing in the target content is actually ambiguous, dense, or under-specified, say so plainly — e.g. "Nothing here looks ambiguous — the request is clear as written." Do not manufacture findings just to have something to report.
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
- `/clearify` — Clarify the most recent user message / relevant conversation context
|
|
51
|
+
- `/clearify <pasted text>` — Clarify the attached text directly
|
|
52
|
+
- `/wtf` — Alias for `/clearify`, invoked via the companion skill in `skills/wtf/`
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: close-story
|
|
3
|
+
description: Close a story by verifying all tasks are in terminal state, extracting actual diff stats per task, computing scope divergence flags, and writing closure metadata to task frontmatter.
|
|
4
|
+
keywords:
|
|
5
|
+
- close story
|
|
6
|
+
- close
|
|
7
|
+
- finish story
|
|
8
|
+
- complete story
|
|
9
|
+
- story done
|
|
10
|
+
examples:
|
|
11
|
+
- "close story E17_S06"
|
|
12
|
+
- "/close-story E32_S06"
|
|
13
|
+
- "mark story E12_S03 as done"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# close-story — Close a Story
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
|
|
20
|
+
`/close-story <story-id>` performs the final closure steps for a story:
|
|
21
|
+
|
|
22
|
+
1. Verifies all tasks in the story are in a terminal state (Passed or Done).
|
|
23
|
+
2. Extracts actual `git diff --stat` metrics for each task from the commit history.
|
|
24
|
+
3. Writes `actual_files_changed`, `actual_lines_delta`, and `scope_divergence_flag` to each task's frontmatter.
|
|
25
|
+
4. Updates the story's frontmatter `status` to `Done` and records `date_completed`.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Instructions
|
|
30
|
+
|
|
31
|
+
### Step 1 — Verify story is closeable
|
|
32
|
+
|
|
33
|
+
Run the guard script:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bash skills/close-story/scripts/check-story-closeable.sh <story-id>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- If it exits with code 0 and prints `CLOSEABLE`, continue.
|
|
40
|
+
- If it exits with code 1, halt and report the blocking tasks to the user. Do not proceed.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Step 2 — Extract per-task diff stats and compute scope divergence
|
|
45
|
+
|
|
46
|
+
**At the start of this step**, read `project/configs/scope-thresholds.json` to check that it
|
|
47
|
+
exists and is parseable. If it is missing or malformed, log a warning and continue — divergence
|
|
48
|
+
flags will be skipped (non-blocking). The `compute-scope-divergence.sh` script handles this
|
|
49
|
+
gracefully by always printing `false` on error.
|
|
50
|
+
|
|
51
|
+
For **each task ID** listed in the story's `tasks:` frontmatter array:
|
|
52
|
+
|
|
53
|
+
1. **Find the task file** at `project/board/tasks/<task-id>_*.md`.
|
|
54
|
+
|
|
55
|
+
2. **Extract commit SHA(s)** for this task:
|
|
56
|
+
```bash
|
|
57
|
+
git log --all --no-merges --grep="<task-id>" --pretty=format:"%H" 2>/dev/null
|
|
58
|
+
```
|
|
59
|
+
This matches commits whose message contains the EST task identifier (e.g. `E32_S06_T01`).
|
|
60
|
+
|
|
61
|
+
3. **Run per-task diff stat extraction:**
|
|
62
|
+
```bash
|
|
63
|
+
bash skills/close-story/scripts/extract-task-diff-stats.sh <task-id>
|
|
64
|
+
```
|
|
65
|
+
The script outputs:
|
|
66
|
+
```
|
|
67
|
+
actual_files_changed: <N>
|
|
68
|
+
actual_lines_delta: <N>
|
|
69
|
+
```
|
|
70
|
+
Parse these values from stdout.
|
|
71
|
+
|
|
72
|
+
4. **Write stats to task frontmatter:**
|
|
73
|
+
```bash
|
|
74
|
+
bash skills/close-story/scripts/update-task-frontmatter.sh \
|
|
75
|
+
"project/board/tasks/<task-id>_*.md" \
|
|
76
|
+
actual_files_changed <N>
|
|
77
|
+
|
|
78
|
+
bash skills/close-story/scripts/update-task-frontmatter.sh \
|
|
79
|
+
"project/board/tasks/<task-id>_*.md" \
|
|
80
|
+
actual_lines_delta <N>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
5. **Compute and write scope divergence flag:**
|
|
84
|
+
|
|
85
|
+
Read the task's `execution_scope` from its frontmatter:
|
|
86
|
+
```bash
|
|
87
|
+
EXECUTION_SCOPE=$(grep '^execution_scope:' "project/board/tasks/<task-id>_*.md" \
|
|
88
|
+
| head -1 | awk '{print $2}')
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If `execution_scope` is empty or absent, treat it as `task` (no divergence).
|
|
92
|
+
|
|
93
|
+
Run the divergence computation:
|
|
94
|
+
```bash
|
|
95
|
+
DIVERGENCE_FLAG=$(bash skills/close-story/scripts/compute-scope-divergence.sh \
|
|
96
|
+
"<execution_scope>" <actual_files_changed> <actual_lines_delta>)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Write the result to task frontmatter:
|
|
100
|
+
```bash
|
|
101
|
+
bash skills/close-story/scripts/update-task-frontmatter.sh \
|
|
102
|
+
"project/board/tasks/<task-id>_*.md" \
|
|
103
|
+
scope_divergence_flag "$DIVERGENCE_FLAG"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Track diverging tasks in a running list for the final report (Step 5).
|
|
107
|
+
|
|
108
|
+
6. **Skip tasks with no matched commits** — If `actual_files_changed` is `0`
|
|
109
|
+
and `actual_lines_delta` is `0`, it likely means the task predates this
|
|
110
|
+
feature or uses an untracked commit. Write the `0` values anyway so the
|
|
111
|
+
field is present but do not treat this as an error. Run divergence
|
|
112
|
+
computation with `0 0` — this will return `false` for all scopes.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### Step 3 — Bundle task attribution
|
|
117
|
+
|
|
118
|
+
A **bundle task** is one where a single commit message contains multiple task
|
|
119
|
+
IDs (e.g. `task(E32_S06_T01, E32_S06_T02): ...`). In this case:
|
|
120
|
+
|
|
121
|
+
- Each task matched by `git log --grep` will independently resolve to the same
|
|
122
|
+
commit SHA, and each will receive the **full bundle stats** as its
|
|
123
|
+
`actual_files_changed` and `actual_lines_delta` values.
|
|
124
|
+
- This is the **"full credit"** attribution model: each task is credited with
|
|
125
|
+
the total work of the bundle commit since the individual contribution cannot
|
|
126
|
+
be mechanically separated.
|
|
127
|
+
- An alternative **proportional split** (divide total stats by number of tasks
|
|
128
|
+
in the bundle) is mathematically cleaner but requires detecting the bundle
|
|
129
|
+
size at query time, which is not supported by the current scripts.
|
|
130
|
+
- The full-credit model is the default. Future work may introduce proportional
|
|
131
|
+
splitting if telemetry shows significant over-counting in bundle scenarios.
|
|
132
|
+
|
|
133
|
+
> Note for tester: bundle attribution means the sum of `actual_lines_delta`
|
|
134
|
+
> across tasks may exceed the true total for stories where bundle commits were
|
|
135
|
+
> used.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Step 4 — Update story frontmatter
|
|
140
|
+
|
|
141
|
+
After all tasks have been updated, set the following fields in the story's
|
|
142
|
+
frontmatter:
|
|
143
|
+
|
|
144
|
+
- `status: Done`
|
|
145
|
+
- `date_completed: <YYYY-MM-DD>` (today's date in UTC)
|
|
146
|
+
|
|
147
|
+
Use `update-task-frontmatter.sh` targeting the story file, or edit directly.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### Step 5 — Report
|
|
152
|
+
|
|
153
|
+
Print a summary to the user:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Story <story-id> closed.
|
|
157
|
+
Tasks updated with actual diff stats:
|
|
158
|
+
<task-id>: files_changed=<N>, lines_delta=<N>, scope_divergence_flag=<true|false>
|
|
159
|
+
...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If any task had `0` for both fields, note it as potentially untracked:
|
|
163
|
+
```
|
|
164
|
+
<task-id>: files_changed=0, lines_delta=0 (no EST-tagged commit found)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Scope Divergence
|
|
168
|
+
|
|
169
|
+
Include a "Scope Divergence" section listing every task whose `scope_divergence_flag` was set
|
|
170
|
+
to `true`. If no tasks diverged, note that explicitly.
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Scope Divergence:
|
|
174
|
+
<task-id> [scope=<execution_scope>]: files_changed=<N>, lines_delta=<N> — exceeded threshold
|
|
175
|
+
...
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Or, if none diverged:
|
|
179
|
+
```
|
|
180
|
+
Scope Divergence: none
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The divergence thresholds applied are those from `project/configs/scope-thresholds.json`:
|
|
184
|
+
- `inline` scope: max_files=<inline_max_files>, max_lines=<inline_max_lines>
|
|
185
|
+
- `story` scope: max_files=<story_max_files>
|
|
186
|
+
- `task` / `epic` scope: no threshold (always false)
|
|
187
|
+
|
|
188
|
+
If the config was missing or malformed, note that divergence flags were skipped:
|
|
189
|
+
```
|
|
190
|
+
Scope Divergence: skipped (scope-thresholds.json missing or malformed)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Scripts Reference
|
|
196
|
+
|
|
197
|
+
| Script | Purpose |
|
|
198
|
+
|--------|---------|
|
|
199
|
+
| `scripts/check-story-closeable.sh <story-id>` | Guard: verify all tasks in terminal state |
|
|
200
|
+
| `scripts/extract-diff-stats.sh <story-id>` | Story-level aggregate diff stats (for story frontmatter) |
|
|
201
|
+
| `scripts/extract-task-diff-stats.sh <task-id>` | Per-task diff stats from EST-tagged commits |
|
|
202
|
+
| `scripts/update-task-frontmatter.sh <file> <key> <value>` | Write/update a YAML field in task or story frontmatter |
|
|
203
|
+
| `scripts/compute-scope-divergence.sh <execution_scope> <actual_files_changed> <actual_lines_delta>` | Compute scope divergence flag: prints `true` or `false` |
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-story-closeable.sh — Last-task detection guard for /close-story
|
|
3
|
+
#
|
|
4
|
+
# Usage: bash check-story-closeable.sh <story-id>
|
|
5
|
+
#
|
|
6
|
+
# Exits 0 and prints "CLOSEABLE" to stdout when all tasks in the story's
|
|
7
|
+
# `tasks:` frontmatter list have status Passed or Done.
|
|
8
|
+
# Exits 1 and prints a descriptive message to stderr for any other outcome.
|
|
9
|
+
#
|
|
10
|
+
# This script always re-reads task files from disk — never uses cached state.
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Helpers
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
usage() {
|
|
19
|
+
echo "Usage: $(basename "$0") <story-id>" >&2
|
|
20
|
+
echo " Example: $(basename "$0") E17_S06" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Locate the project root relative to this script's location.
|
|
25
|
+
# Script lives at: skills/close-story/scripts/check-story-closeable.sh
|
|
26
|
+
# Project root is three levels up (skills/ → repo root).
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
29
|
+
STORIES_DIR="$PROJECT_ROOT/project/board/stories"
|
|
30
|
+
TASKS_DIR="$PROJECT_ROOT/project/board/tasks"
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Argument validation
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
if [[ $# -ne 1 ]]; then
|
|
37
|
+
usage
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
STORY_ID="$1"
|
|
41
|
+
|
|
42
|
+
# Basic sanity check on the story ID format (E##_S##)
|
|
43
|
+
if [[ ! "$STORY_ID" =~ ^E[0-9]{2}_S[0-9]{2}$ ]]; then
|
|
44
|
+
echo "ERROR: Invalid story ID format '$STORY_ID'. Expected format: E##_S## (e.g. E17_S06)" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Step 1: Locate story file
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
# Use a glob — story slug may vary, so match on ID prefix only.
|
|
53
|
+
story_file=""
|
|
54
|
+
while IFS= read -r -d '' candidate; do
|
|
55
|
+
story_file="$candidate"
|
|
56
|
+
break
|
|
57
|
+
done < <(find "$STORIES_DIR" -maxdepth 1 -name "${STORY_ID}_*.md" -print0 2>/dev/null)
|
|
58
|
+
|
|
59
|
+
if [[ -z "$story_file" ]]; then
|
|
60
|
+
echo "ERROR: Story file not found for '$STORY_ID' in $STORIES_DIR" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Step 2: Extract tasks: list from story frontmatter
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Frontmatter is the YAML block between the first pair of --- delimiters.
|
|
68
|
+
# The tasks: field is a YAML list of task IDs, e.g.:
|
|
69
|
+
# tasks:
|
|
70
|
+
# - E17_S06_T01
|
|
71
|
+
# - E17_S06_T02
|
|
72
|
+
|
|
73
|
+
extract_tasks() {
|
|
74
|
+
local file="$1"
|
|
75
|
+
local in_frontmatter=0
|
|
76
|
+
local in_tasks=0
|
|
77
|
+
local found_any=0
|
|
78
|
+
|
|
79
|
+
while IFS= read -r line; do
|
|
80
|
+
# Detect frontmatter boundaries
|
|
81
|
+
if [[ "$line" == "---" ]]; then
|
|
82
|
+
if [[ $in_frontmatter -eq 0 ]]; then
|
|
83
|
+
in_frontmatter=1
|
|
84
|
+
continue
|
|
85
|
+
else
|
|
86
|
+
# Closing --- ends the frontmatter block
|
|
87
|
+
break
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Only process lines inside frontmatter
|
|
92
|
+
if [[ $in_frontmatter -eq 0 ]]; then
|
|
93
|
+
continue
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Detect the start of the tasks: key
|
|
97
|
+
if [[ "$line" =~ ^tasks:[[:space:]]*$ ]]; then
|
|
98
|
+
in_tasks=1
|
|
99
|
+
continue
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# If we are collecting tasks, look for list items
|
|
103
|
+
if [[ $in_tasks -eq 1 ]]; then
|
|
104
|
+
# A list item starts with optional whitespace then "- "
|
|
105
|
+
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]+([A-Za-z0-9_]+) ]]; then
|
|
106
|
+
echo "${BASH_REMATCH[1]}"
|
|
107
|
+
found_any=1
|
|
108
|
+
elif [[ "$line" =~ ^[^[:space:]] ]]; then
|
|
109
|
+
# A non-indented, non-list line means we've left the tasks block
|
|
110
|
+
break
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
done < "$file"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
mapfile -t TASK_IDS < <(extract_tasks "$story_file")
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Step 3: Validate tasks: list is present and non-empty
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
if [[ ${#TASK_IDS[@]} -eq 0 ]]; then
|
|
123
|
+
echo "ERROR: No tasks found in story frontmatter for '$STORY_ID' — cannot determine close state" >&2
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Step 4: Check each task's status
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
all_closeable=1
|
|
132
|
+
|
|
133
|
+
for task_id in "${TASK_IDS[@]}"; do
|
|
134
|
+
# 4a. Locate the task file (always re-read from disk)
|
|
135
|
+
task_file=""
|
|
136
|
+
while IFS= read -r -d '' candidate; do
|
|
137
|
+
task_file="$candidate"
|
|
138
|
+
break
|
|
139
|
+
done < <(find "$TASKS_DIR" -maxdepth 1 -name "${task_id}_*.md" -print0 2>/dev/null)
|
|
140
|
+
|
|
141
|
+
if [[ -z "$task_file" ]]; then
|
|
142
|
+
echo "ERROR: Task file not found for '$task_id' — treating as open" >&2
|
|
143
|
+
all_closeable=0
|
|
144
|
+
continue
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# 4b/4c. Extract status: field from the task's frontmatter
|
|
148
|
+
task_status=""
|
|
149
|
+
in_fm=0
|
|
150
|
+
while IFS= read -r line; do
|
|
151
|
+
if [[ "$line" == "---" ]]; then
|
|
152
|
+
if [[ $in_fm -eq 0 ]]; then
|
|
153
|
+
in_fm=1
|
|
154
|
+
continue
|
|
155
|
+
else
|
|
156
|
+
break
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
if [[ $in_fm -eq 1 && "$line" =~ ^status:[[:space:]]*(.+)$ ]]; then
|
|
160
|
+
task_status="${BASH_REMATCH[1]}"
|
|
161
|
+
# Trim surrounding quotes if present
|
|
162
|
+
task_status="${task_status%\"}"
|
|
163
|
+
task_status="${task_status#\"}"
|
|
164
|
+
task_status="${task_status%\'}"
|
|
165
|
+
task_status="${task_status#\'}"
|
|
166
|
+
break
|
|
167
|
+
fi
|
|
168
|
+
done < "$task_file"
|
|
169
|
+
|
|
170
|
+
# 4d. Evaluate status
|
|
171
|
+
case "$task_status" in
|
|
172
|
+
Passed|Done)
|
|
173
|
+
# Task is in a terminal state — OK
|
|
174
|
+
;;
|
|
175
|
+
"")
|
|
176
|
+
echo "OPEN: '$task_id' has no status field — treating as open" >&2
|
|
177
|
+
all_closeable=0
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
echo "OPEN: '$task_id' has status '$task_status'" >&2
|
|
181
|
+
all_closeable=0
|
|
182
|
+
;;
|
|
183
|
+
esac
|
|
184
|
+
done
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# Step 5: Report result
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
if [[ $all_closeable -eq 1 ]]; then
|
|
191
|
+
echo "CLOSEABLE"
|
|
192
|
+
exit 0
|
|
193
|
+
else
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|