@jenga-ai/agent 1.1.0 → 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.
Files changed (91) hide show
  1. package/README.md +7 -3
  2. package/agents/developer.md +82 -2
  3. package/agents/scrum-master.md +140 -21
  4. package/agents/tester.md +90 -8
  5. package/hooks/on_session_end.sh +171 -20
  6. package/package.json +1 -1
  7. package/scripts/check-permission-level.sh +107 -0
  8. package/scripts/check-publicignore-match.sh +122 -0
  9. package/scripts/check-worktree-liveness.sh +193 -0
  10. package/scripts/generate-rapport-manifest.sh +43 -0
  11. package/scripts/idea_manager.sh +47 -0
  12. package/scripts/install-worktree-commit-guard.sh +134 -0
  13. package/scripts/jenga-permission-level-switch.sh +109 -0
  14. package/scripts/smoke-harness.sh +139 -0
  15. package/scripts/validate-board.sh +62 -0
  16. package/scripts/with-lock.sh +158 -0
  17. package/scripts/worktree-remove-guard.sh +204 -0
  18. package/skills/clearify/SKILL.md +52 -0
  19. package/skills/commit/SKILL.md +13 -4
  20. package/skills/distribute/CONFIG_SCHEMA.md +60 -2
  21. package/skills/do/SKILL.md +48 -11
  22. package/skills/doc-sync/SKILL.md +16 -0
  23. package/skills/doc-sync/assets/doc_targets.md +11 -0
  24. package/skills/idea/SKILL.md +56 -0
  25. package/skills/idea/assets/idea_handoff_template.md +26 -0
  26. package/skills/idea/assets/idea_template.md +3 -0
  27. package/skills/init/SKILL.md +100 -7
  28. package/skills/init/assets/directory_structure.txt +1 -0
  29. package/skills/init/assets/workflow_template.json +1 -1
  30. package/skills/init/scripts/apply-project-visibility.sh +176 -0
  31. package/skills/init/scripts/detect-existing-codebase.sh +166 -0
  32. package/skills/init/scripts/init.sh +30 -1
  33. package/skills/jenga/SKILL.md +160 -17
  34. package/skills/jenga/scripts/board-scan.sh +238 -0
  35. package/skills/jenga/scripts/cascade-resolve.sh +297 -0
  36. package/skills/jenga/scripts/render-confirmation.sh +679 -0
  37. package/skills/jenga/scripts/render-picker.sh +439 -0
  38. package/skills/jenga/scripts/resolve-id.sh +367 -0
  39. package/skills/jenga-permission-level/SKILL.md +81 -0
  40. package/skills/proceed/SKILL.md +1 -1
  41. package/skills/publish/SKILL.md +8 -5
  42. package/skills/publish/assets/ci-contract.md +2 -2
  43. package/skills/publish/assets/ownership-matrix.md +1 -1
  44. package/skills/publish/scripts/finalize_changelog.sh +115 -0
  45. package/skills/publish/scripts/generate_release_notes.sh +475 -28
  46. package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
  47. package/skills/publish/scripts/publish_deploy.sh +38 -8
  48. package/skills/publish/scripts/run_gates.sh +2 -2
  49. package/skills/reconcile/SKILL.md +117 -5
  50. package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
  51. package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
  52. package/skills/spinoff/SKILL.md +12 -7
  53. package/skills/todo/SKILL.md +2 -0
  54. package/skills/uncharted/SKILL.md +711 -0
  55. package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
  56. package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
  57. package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
  58. package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
  59. package/skills/uncharted/scripts/detect-tests.sh +553 -0
  60. package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
  61. package/skills/uncharted/scripts/enumerate-target.sh +470 -0
  62. package/skills/uncharted/scripts/import-source.sh +517 -0
  63. package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
  64. package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
  65. package/skills/uncharted/scripts/run-engine.sh +655 -0
  66. package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
  67. package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
  68. package/skills/wtf/SKILL.md +20 -0
  69. package/templates/CHANGELOG_TEMPLATE.md +13 -0
  70. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
  71. package/templates/SCRUM_BOARD_SCHEMA.md +157 -10
  72. package/templates/permission-levels/README.md +73 -0
  73. package/templates/permission-levels/level-1-locked.json +71 -0
  74. package/templates/permission-levels/level-2-guarded.json +64 -0
  75. package/templates/permission-levels/level-3-standard.json +62 -0
  76. package/templates/permission-levels/level-4-elevated.json +60 -0
  77. package/templates/permission-levels/level-5-unrestricted.json +58 -0
  78. package/skills/convert/SKILL.md +0 -124
  79. package/skills/convert/convert_cli.py +0 -235
  80. package/skills/convert/tests/sample.csv +0 -4
  81. package/skills/convert/tests/sample.json +0 -5
  82. package/skills/convert/tests/sample.jsonl +0 -3
  83. package/skills/convert/tests/sample.yaml +0 -18
  84. package/skills/convert/tests/sample_obj.csv +0 -2
  85. package/skills/convert/tests/sample_obj.json +0 -9
  86. package/skills/mirror-public/SKILL.md +0 -237
  87. package/skills/mirror-public/assets/config.json +0 -5
  88. package/skills/mirror-public/scripts/mirror.sh +0 -374
  89. package/skills/self-sync/SKILL.md +0 -73
  90. package/skills/self-sync/scripts/run.js +0 -136
  91. package/skills/strategy/SKILL.md +0 -312
@@ -27,18 +27,76 @@ STATUS_VALUES = {
27
27
  "Done",
28
28
  }
29
29
 
30
+ # E39 tiered item-level caution/escalation. crucial_level is OPTIONAL — absence means no
31
+ # elevated caution. When present it must be one of these three values (see
32
+ # templates/SCRUM_BOARD_SCHEMA.md). crucial_set_by / crucial_note are free-text fields that are
33
+ # only recognised (not enum-validated) here; their "required when crucial_level is set" rule is
34
+ # an authoring discipline documented in the schema, not mechanically enforced by this script —
35
+ # the same treatment given to scope_rationale's "required when execution_scope is set" rule.
36
+ CRUCIAL_LEVEL_VALUES = {
37
+ "advisory",
38
+ "gated",
39
+ "locked",
40
+ }
41
+
42
+ # Base keys shared by every item type, plus the E32 adaptive-execution-scope fields.
43
+ #
44
+ # The execution-scope fields are all OPTIONAL — a board file that omits them is valid and is
45
+ # treated as execution_scope: task / needs_docs: true (see the backward-compatibility rule in
46
+ # templates/SCRUM_BOARD_SCHEMA.md). They are listed here so that files which DO carry them are
47
+ # not rejected as unknown.
48
+ #
49
+ # Two groups, distinguished by who writes them:
50
+ # - assigned at breakdown time by the scrum-master (execution_scope … epic_scope_approval)
51
+ # - written at runtime by /close-story and /do (actual_files_changed … divergence_flag)
52
+ # Runtime fields appear only after a task has executed, so most task files will lack them.
53
+ # task_changed_files is deliberately absent: it lives in the bundle manifest JSON
54
+ # (project/queue/bundle-<E##_S##>.json), not in task frontmatter.
55
+ EXECUTION_SCOPE_KEYS = {
56
+ "execution_scope", "needs_docs", "scope_rationale",
57
+ "jenga_assigned", "override_justification", "epic_scope_approval",
58
+ }
59
+
60
+ CLOSE_STORY_KEYS = {
61
+ "actual_files_changed", "actual_lines_delta", "scope_divergence_flag", "divergence_flag",
62
+ }
63
+
64
+ # E39 tiered item-level caution/escalation fields. OPTIONAL and story/task-only — epics do not
65
+ # carry these; an epic's risk gating is already handled by epic_scope_approval. See
66
+ # templates/SCRUM_BOARD_SCHEMA.md.
67
+ #
68
+ # crucial_declined / crucial_declined_note (E39_S02_T03) are a separate optional pair recording
69
+ # that a scrum-master-proposed crucial_level was explicitly declined by the user for this item —
70
+ # the durable decline-tracking mechanism that stops the breakdown step from re-proposing on a
71
+ # later pass. Like crucial_set_by / crucial_note, they are free-text/recognised-not-enum-validated
72
+ # here; their "required when crucial_declined: true" rule is an authoring discipline documented in
73
+ # the schema, not mechanically enforced by this script.
74
+ CRUCIAL_KEYS = {
75
+ "crucial_level", "crucial_set_by", "crucial_note",
76
+ "crucial_declined", "crucial_declined_note",
77
+ }
78
+
30
79
  ALLOWED_KEYS = {
31
80
  "epic": {
32
81
  "id", "title", "status", "date_created", "date_started", "date_completed",
33
82
  "dates_previously_completed", "reopened_on", "reopened_reason", "stories", "docs",
83
+ "epic_scope_approval",
84
+ # OPTIONAL. Marks how the epic came to exist; only written by `/uncharted onboard`
85
+ # (value: backfilled). Absence means the epic was authored normally, so every existing
86
+ # epic that omits it stays valid. See templates/SCRUM_BOARD_SCHEMA.md.
87
+ "provenance",
34
88
  },
35
89
  "story": {
36
90
  "id", "epic_id", "title", "status", "date_created", "date_started", "date_completed",
37
91
  "dates_previously_completed", "reopened_on", "reopened_reason", "tasks", "docs",
92
+ "priority", "depends_on",
93
+ *CRUCIAL_KEYS,
38
94
  },
39
95
  "task": {
40
96
  "id", "story_id", "epic_id", "title", "status", "date_created", "date_started", "date_completed",
41
97
  "dates_previously_completed", "reopened_on", "reopened_reason", "assigned_to", "docs",
98
+ "depends_on",
99
+ *EXECUTION_SCOPE_KEYS, *CLOSE_STORY_KEYS, *CRUCIAL_KEYS,
42
100
  },
43
101
  }
44
102
 
@@ -152,6 +210,10 @@ def validate_file(source: Path):
152
210
  if status and status not in STATUS_VALUES:
153
211
  raise ValueError(f"{source}: invalid status '{status}'")
154
212
 
213
+ crucial_level = data.get("crucial_level")
214
+ if crucial_level and crucial_level not in CRUCIAL_LEVEL_VALUES:
215
+ raise ValueError(f"{source}: invalid crucial_level '{crucial_level}'")
216
+
155
217
  docs = data.get("docs", None)
156
218
  if docs is not None:
157
219
  validate_docs(source, docs)
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/with-lock.sh — atomic, cross-platform exclusive file lock wrapper
3
+ #
4
+ # Usage:
5
+ # scripts/with-lock.sh <target-file> -- <command> [args...]
6
+ #
7
+ # Acquires an exclusive lock keyed to <target-file> before running <command>,
8
+ # and always releases the lock afterward — on success, on failure, and on
9
+ # signal (INT/TERM sent to this script are forwarded to the wrapped command,
10
+ # which is then waited on before the lock is released — see the signal
11
+ # handling note further down). The wrapped command's exit code is preserved
12
+ # as this script's own exit code.
13
+ #
14
+ # This replaces the purely advisory "check for a .lock file, wait, retry,
15
+ # then create/delete it yourself" convention previously described in
16
+ # templates/SCRUM_BOARD_SCHEMA.md. That convention relied on every caller
17
+ # implementing check-wait-retry-cleanup correctly, including on error paths,
18
+ # with nothing to mechanically stop two writers from both deciding the lock
19
+ # is free at the same time. This script enforces exclusivity instead.
20
+ #
21
+ # Lock primitive: `mkdir` on a lock directory, NOT `flock`. `flock` is a
22
+ # Linux-only (util-linux) utility and is not present by default on macOS/BSD
23
+ # (this repo runs on Darwin). POSIX `mkdir` is atomic on every platform this
24
+ # repo targets: the kernel guarantees that when multiple processes race to
25
+ # create the same directory, exactly one succeeds and every other call fails
26
+ # with EEXIST. That atomicity — not any cooperative check beforehand — is
27
+ # what makes this a real mutual-exclusion lock instead of advisory prose.
28
+ #
29
+ # Environment overrides (all optional):
30
+ # WITH_LOCK_TIMEOUT_SECONDS Max time to wait for a held lock (default: 30)
31
+ # WITH_LOCK_POLL_SECONDS Poll interval while waiting (default: 0.2)
32
+ # WITH_LOCK_STALE_SECONDS Age after which a still-held lock is treated
33
+ # as abandoned (crashed holder) and reclaimed
34
+ # (default: 60)
35
+ #
36
+ # Exit codes:
37
+ # 0 lock acquired, command ran, exit code is the command's own
38
+ # 1 invalid usage
39
+ # 2 could not acquire the lock within the timeout — the command was
40
+ # NEVER run (fail safe; no partial or silently-clobbered write)
41
+ # * otherwise, the wrapped command's own exit code
42
+
43
+ set -u
44
+
45
+ usage() {
46
+ echo "Usage: $0 <target-file> -- <command> [args...]" >&2
47
+ exit 1
48
+ }
49
+
50
+ [ "$#" -ge 1 ] || usage
51
+ TARGET_FILE="$1"
52
+ shift
53
+
54
+ [ "${1:-}" = "--" ] || usage
55
+ shift
56
+
57
+ [ "$#" -ge 1 ] || usage
58
+
59
+ TIMEOUT_SECONDS="${WITH_LOCK_TIMEOUT_SECONDS:-30}"
60
+ POLL_SECONDS="${WITH_LOCK_POLL_SECONDS:-0.2}"
61
+ STALE_SECONDS="${WITH_LOCK_STALE_SECONDS:-60}"
62
+
63
+ LOCK_DIR="${TARGET_FILE}.lock.d"
64
+ LOCK_PID_FILE="${LOCK_DIR}/pid"
65
+
66
+ now_epoch() {
67
+ date +%s
68
+ }
69
+
70
+ # Portable mtime lookup: GNU stat (Linux) uses -c, BSD stat (macOS) uses -f.
71
+ # Try GNU form first, fall back to BSD form. Echoes nothing (and returns
72
+ # non-zero) if the directory vanished in the meantime.
73
+ lock_mtime_epoch() {
74
+ stat -c %Y "$LOCK_DIR" 2>/dev/null || stat -f %m "$LOCK_DIR" 2>/dev/null
75
+ }
76
+
77
+ # If the held lock looks abandoned (older than STALE_SECONDS), reclaim it.
78
+ # This does NOT grant the lock by itself — it only clears the way. The next
79
+ # mkdir attempt in the polling loop is still the sole arbiter of who
80
+ # proceeds, so simultaneous reclaim attempts by multiple waiters never let
81
+ # more than one of them through.
82
+ maybe_reclaim_stale_lock() {
83
+ local mtime now age
84
+ mtime="$(lock_mtime_epoch)" || return 0
85
+ now="$(now_epoch)"
86
+ age=$(( now - mtime ))
87
+ if [ "$age" -ge "$STALE_SECONDS" ]; then
88
+ echo "with-lock: reclaiming stale lock '$LOCK_DIR' (age ${age}s >= ${STALE_SECONDS}s)" >&2
89
+ rm -rf "$LOCK_DIR" 2>/dev/null
90
+ fi
91
+ }
92
+
93
+ acquire_lock() {
94
+ local start now elapsed
95
+ start="$(now_epoch)"
96
+ while true; do
97
+ if mkdir "$LOCK_DIR" 2>/dev/null; then
98
+ echo "$$" > "$LOCK_PID_FILE" 2>/dev/null || true
99
+ return 0
100
+ fi
101
+
102
+ maybe_reclaim_stale_lock
103
+
104
+ now="$(now_epoch)"
105
+ elapsed=$(( now - start ))
106
+ if [ "$elapsed" -ge "$TIMEOUT_SECONDS" ]; then
107
+ return 1
108
+ fi
109
+ sleep "$POLL_SECONDS" 2>/dev/null || sleep 1
110
+ done
111
+ }
112
+
113
+ release_lock() {
114
+ rm -rf "$LOCK_DIR" 2>/dev/null
115
+ }
116
+
117
+ if ! acquire_lock; then
118
+ echo "with-lock: failed to acquire lock on '$TARGET_FILE' within ${TIMEOUT_SECONDS}s (lock dir: $LOCK_DIR) — command was not run" >&2
119
+ exit 2
120
+ fi
121
+
122
+ # Always release the lock on exit, regardless of how the wrapped command
123
+ # (or this script itself) terminates.
124
+ trap release_lock EXIT
125
+
126
+ # Signal handling note: the wrapped command is run in the background and
127
+ # waited on explicitly (rather than exec'd directly in the foreground) so
128
+ # that INT/TERM delivered to this script are handled promptly. Bash defers
129
+ # non-EXIT traps until a foreground command completes, so a script that
130
+ # instead ran `"$@"` directly in the foreground would not react to a signal
131
+ # sent to its own PID until the wrapped command finished on its own —
132
+ # release would then only happen once the command exited naturally (or, in
133
+ # the worst case, once WITH_LOCK_STALE_SECONDS elapsed and another waiter
134
+ # reclaimed the lock). Backgrounding + `wait` avoids that: the trap fires as
135
+ # soon as the signal arrives, forwards it to the child, waits for the child
136
+ # to actually exit, then this script exits (triggering the EXIT trap above,
137
+ # which releases the lock).
138
+ CHILD_PID=""
139
+
140
+ forward_signal_and_exit() {
141
+ local sig="$1" exit_code="$2"
142
+ if [ -n "$CHILD_PID" ]; then
143
+ kill -s "$sig" "$CHILD_PID" 2>/dev/null
144
+ wait "$CHILD_PID" 2>/dev/null
145
+ fi
146
+ exit "$exit_code"
147
+ }
148
+
149
+ trap 'forward_signal_and_exit TERM 143' TERM
150
+ trap 'forward_signal_and_exit INT 130' INT
151
+
152
+ "$@" &
153
+ CHILD_PID=$!
154
+ wait "$CHILD_PID"
155
+ status=$?
156
+ CHILD_PID=""
157
+
158
+ exit "$status"
@@ -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/`
@@ -18,7 +18,7 @@ examples:
18
18
 
19
19
  When invoked with the `--inline` flag OR when the environment variable `JENGA_COMMIT_INLINE=1` is set, execute inline mode:
20
20
 
21
- 1. **Skip** the user-action prerequisites check (step 1 in normal mode). No `_INSTRUCTIONS.md` lookup is performed.
21
+ 1. **Skip** the reconcile step, the `/doc-sync` scan, and the user-action prerequisites check (steps 1-3 in normal mode). No `/reconcile` invocation, `/doc-sync` invocation, or `_INSTRUCTIONS.md` lookup is performed — inline tasks are single, already-scoped-small changes that reconcile and doc-sync would add overhead to, not risk, disproportionate to the size of the change.
22
22
  2. **Skip** any worktree merge logic — inline tasks execute in the main session with no dedicated worktree to merge.
23
23
  3. Stage all changed files relevant to the task (use `git add -A` or specific files if a list was provided by the caller).
24
24
  4. Commit using the EST naming convention:
@@ -36,12 +36,21 @@ If `--inline` is absent **and** `JENGA_COMMIT_INLINE` is not set (or is not `1`)
36
36
 
37
37
  If no epic, task, or story has been implemented, exit with the message: "No implementation to commit."
38
38
 
39
- 1. **Verify user-action prerequisites** — Check whether an `_INSTRUCTIONS.md` file exists for this task at `project/board/tasks/<E##_S##_T##>_INSTRUCTIONS.md`. If the task has out-of-scope prerequisites but no instructions file was created, create one now using `assets/user_instructions_template.md`. If one already exists, surface it to the user as a reminder. (The developer should have created this file during task intake — this is a final safety check.)
39
+ 1. **Reconcile first** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
40
+ - **If reconcile detects and corrects drift** — inform the user what changed (e.g. demoted/promoted statuses, merged orphaned worktrees, cleaned `todo.md` entries) before proceeding.
41
+ - **If reconcile finds no drift** — continue silently to the next step.
40
42
 
41
- 2. **Commit** using the following format:
43
+ 2. **Doc-sync scan, scoped to this change (report-only, non-blocking)** — Invoke `/doc-sync` scoped via its `source:` argument to the files actually changed by the work being committed — never a full-repo scan. Determine the changed-file list from the work being committed (e.g. `git diff --name-only HEAD` combined with untracked files from `git status --porcelain`, or the task's known changed-file set when commit context already identifies them) and pass it as `source:` so doc-sync only analyses what this commit touches.
44
+ - **Design decision — report-only, not blocking:** doc-sync's own step 5 ("report findings, ask before applying") is a human-approval gate. When invoked from `/commit`, doc-sync runs only through its own step 5 (report the drift findings) and explicitly does **not** proceed to its step 6 (apply updates) as part of this flow — `/commit` never surfaces doc-sync's apply-confirmation prompt mid-commit. *Rationale: nesting a second approval gate inside an already-in-flight commit either stalls a flow the user expected to complete in one shot, or trains the user to reflexively decline the nested prompt just to get their commit through. Report-only surfaces documentation drift at the cheapest possible moment to notice it — right when the change is fresh — without forcing an apply/skip decision under commit pressure; the user reviews the findings and runs `/doc-sync` standalone afterward if they want to apply them.* This introduces no change to `/doc-sync`'s own step 5 approval semantics and no new auto-apply flag — `/commit` simply never invites it past step 5.
45
+ - **If doc-sync reports drift findings** — surface them to the user as part of the commit output (informational), then continue to the next step regardless of the findings.
46
+ - **If doc-sync reports no drift** — continue silently to the next step.
47
+
48
+ 3. **Verify user-action prerequisites** — Check whether an `_INSTRUCTIONS.md` file exists for this task at `project/instructions/<E##_S##_T##>_INSTRUCTIONS.md`. If the task has out-of-scope prerequisites but no instructions file was created, create one now using `assets/user_instructions_template.md`. If one already exists, surface it to the user as a reminder. (The developer should have created this file during task intake — this is a final safety check.)
49
+
50
+ 4. **Commit** using the following format:
42
51
  - **Epic:** `epic(<Epic Title>): <MAX_50_CHAR_SUMMARY>`
43
52
  - **Task/Story:** `story(<Epic Title>_<Story Title>): <MAX_50_CHAR_SUMMARY>`
44
53
 
45
54
  **Fallback: Group changes logically** — prefer one commit per coherent unit of work, but don't force splits. When in doubt, keep it together.
46
55
 
47
- 3. **Check for next epic** — If a new epic is to be started, inform the user that a new conversation should be initiated. If there are no subsequent epics left, show the message: "All Done! 🎉"
56
+ 5. **Check for next epic** — If a new epic is to be started, inform the user that a new conversation should be initiated. If there are no subsequent epics left, show the message: "All Done! 🎉"
@@ -1,6 +1,6 @@
1
1
  # jenga.config.json — Schema Reference
2
2
 
3
- This document is the canonical reference for the `jenga.config.json` file written into **consuming projects** during framework distribution. The file is created and maintained by `distribute-changes.sh`; it should not be edited by hand.
3
+ This document is the canonical reference for the `jenga.config.json` file written into **consuming projects** during framework distribution. The file is created and maintained by `distribute-changes.sh`, with the `project_files_visibility` field written by `skills/init/scripts/apply-project-visibility.sh` during `/init`; it should not be edited by hand.
4
4
 
5
5
  ---
6
6
 
@@ -27,7 +27,8 @@ This document is the canonical reference for the `jenga.config.json` file writte
27
27
  "version": "2.3.1",
28
28
  "updated_at": "2026-08-11",
29
29
  "last_distributed": "2026-08-11T10:00:00Z",
30
- "source": "private"
30
+ "source": "private",
31
+ "project_files_visibility": "visible"
31
32
  }
32
33
  ```
33
34
 
@@ -43,6 +44,61 @@ This document is the canonical reference for the `jenga.config.json` file writte
43
44
  | `updated_at` | string (ISO 8601 date) | yes | — | Date of the last successful distribution, in `YYYY-MM-DD` format. Does **not** include a time component. |
44
45
  | `last_distributed` | string (ISO 8601 datetime) | yes | — | Full UTC timestamp of the last successful distribution, in `YYYY-MM-DDTHH:MM:SSZ` format. Provides more precision than `updated_at` and is useful for audit and ordering purposes. |
45
46
  | `source` | string | yes | `"private"` | Distribution channel. Always `"private"` for projects that receive updates via the filesystem distribution mechanism. Distinguishes these projects from any future npm-installed consumers. Do not change this value manually. |
47
+ | `project_files_visibility` | string (enum) | no | `"visible"` | How JengaAgent's own working files appear in the consuming project. Exactly one of `visible` or `ignored` — no other value is accepted. Written by `/init`, not by distribution. See [Project files visibility](#project-files-visibility) below. |
48
+
49
+ ---
50
+
51
+ ## Project files visibility
52
+
53
+ `project_files_visibility` controls how JengaAgent's own working files — the `project/` tree containing the scrum board, `todo.md`, `queue/`, `rapports/`, and `logs/` — appear in a consuming project.
54
+
55
+ This is distinct from `target_dir`. `target_dir` governs where the **distributed framework files** (skill and agent definitions) land; `project_files_visibility` governs the **working tree** that accumulates as the framework is used.
56
+
57
+ ### Allowed values
58
+
59
+ Exactly two values are accepted. Any other value is rejected with a non-zero exit code.
60
+
61
+ | Value | On-disk effect |
62
+ |---|---|
63
+ | `visible` | Working files stay at `project/`, tracked and visible in directory listings. Nothing on disk is changed. |
64
+ | `ignored` | Working files stay at `project/`, but `project/` is appended to the project's `.gitignore`, so they exist on disk and are never committed. |
65
+
66
+ #### Withdrawn: `hidden`
67
+
68
+ A third value, `hidden` (dot-prefixing `project/` to `.project/`, following the
69
+ same convention already used for `.agents/` and `.claude/`), was implemented
70
+ and tester-verified to produce the correct on-disk layout, but was withdrawn
71
+ before release because it is functionally broken at runtime:
72
+
73
+ - `scripts/board_resolver.sh` hardcodes `project/configs/workflow.json` as its
74
+ config path. It never locates the rewritten `.project/configs/workflow.json`,
75
+ silently falls back to a default that no longer exists, and exits `0` —
76
+ a silent wrong answer rather than a hard failure.
77
+ - `hooks/on_session_end.sh` hardcodes and unconditionally creates
78
+ `project/rapports/problems`, `project/queue`, and `project/logs`. Under
79
+ `hidden` mode this recreates a shadow `project/` tree on the very next
80
+ session end, splitting runtime state across `project/` and `.project/` —
81
+ the clutter the mode was meant to remove reappears, and some agent output
82
+ (e.g. queue triggers) is written to the tree the board no longer lives in.
83
+
84
+ Full findings: `project/rapports/problems/E31_S05_T01-hidden-mode-path-resolution-gaps.md`.
85
+
86
+ `hidden` is not offered by the `/init` prompt and is not accepted by
87
+ `apply-project-visibility.sh`. Reintroducing it requires fixing both hardcoded
88
+ paths above (routing them through `workflow.json` instead) — tracked as a
89
+ follow-up `/todo` item rather than built as part of this field.
90
+
91
+ ### Default
92
+
93
+ The default is **`visible`**, used whenever `/init` runs non-interactively or the user is not prompted.
94
+
95
+ `visible` is the only value that is a genuine no-op on disk, so an unattended run can never silently relocate a user's directories or mutate their `.gitignore`. It also matches the behaviour of every project scaffolded before this field existed, making it backward-compatible for any consuming project whose `jenga.config.json` predates the field — an absent field is read as `visible`.
96
+
97
+ ### Who writes it
98
+
99
+ The field is written during `/init` by `skills/init/scripts/apply-project-visibility.sh`, which also performs the corresponding on-disk change. The script merges the field into any existing `jenga.config.json` rather than overwriting the file, since `/init` normally runs before the first `/distribute` has created it.
100
+
101
+ Changing the value after the initial `/init` is not currently supported — there is no toggle or migration path. Re-running the applier with a different mode is not a supported upgrade route.
46
102
 
47
103
  ---
48
104
 
@@ -59,6 +115,8 @@ When `distribute-changes.sh` runs against a project for the first time and no `j
59
115
 
60
116
  The directory referenced by `target_dir` is created if it does not already exist.
61
117
 
118
+ > **Known gap:** `distribute-changes.sh` rebuilds `jenga.config.json` from scratch on every run and emits only the six fields above, so a `project_files_visibility` value written by `/init` is dropped by the next `/distribute`. Making the rebuild preserve fields it does not own is tracked as follow-up work; until then, treat the on-disk layout (not the config field) as the source of truth for which mode a project is in.
119
+
62
120
  ---
63
121
 
64
122
  ## Atomic write mechanism