@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.
- package/README.md +7 -3
- package/agents/developer.md +82 -2
- package/agents/scrum-master.md +140 -21
- package/agents/tester.md +90 -8
- package/hooks/on_session_end.sh +171 -20
- package/package.json +1 -1
- 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/commit/SKILL.md +13 -4
- package/skills/distribute/CONFIG_SCHEMA.md +60 -2
- package/skills/do/SKILL.md +48 -11
- 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 +100 -7
- package/skills/init/assets/directory_structure.txt +1 -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 +30 -1
- package/skills/jenga/SKILL.md +160 -17
- 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 +157 -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/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/`
|
package/skills/commit/SKILL.md
CHANGED
|
@@ -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 (
|
|
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. **
|
|
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. **
|
|
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
|
-
|
|
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
|