@jenga-ai/agent 3.6.0 → 4.0.0
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/lib/generate-skill-allow-list.js +42 -5
- package/lib/skill-allow-list.json +1 -1
- package/package.json +1 -2
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
- package/project/app/ui/dist/index.html +2 -2
- package/scripts/acquire-concurrency-slot.sh +34 -4
- package/scripts/apply-j-prefix.sh +1 -1
- package/scripts/delete-bare-skill-dirs.sh +1 -2
- package/scripts/idea_manager.sh +16 -1
- package/scripts/release-concurrency-slot.sh +34 -4
- package/scripts/render-ranked-list.sh +270 -0
- package/scripts/repoint-dead-bare-path-prose.py +81 -0
- package/scripts/rewrite-stale-skill-preambles.py +188 -0
- package/scripts/strip-polyfill-frontmatter.py +166 -0
- package/scripts/todo_manager.sh +16 -1
- package/scripts/validate-typed-object.sh +750 -0
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +3 -4
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
- package/skills/j-commit/SKILL.md +3 -4
- package/skills/j-continue/SKILL.md +5 -6
- package/skills/j-deep-dive/SKILL.md +3 -4
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +13 -15
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc-sync/SKILL.md +3 -4
- package/skills/j-dooo/SKILL.md +6 -15
- package/skills/j-error/SKILL.md +3 -4
- package/skills/j-evaluate/SKILL.md +3 -4
- package/skills/j-examplify/SKILL.md +3 -4
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +3 -4
- package/skills/j-improve/SKILL.md +3 -4
- package/skills/j-init/SKILL.md +20 -11
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
- package/skills/j-jbp/SKILL.md +3 -4
- package/skills/j-lgtm/SKILL.md +3 -4
- package/skills/j-pi-plan/SKILL.md +3 -4
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +3 -4
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +38 -5
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +3 -4
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +3 -4
- package/skills/j-status/SKILL.md +4 -5
- package/skills/j-todo/SKILL.md +42 -5
- package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
- package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
- package/skills/j-uncharted/SKILL.md +251 -12
- package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
- package/skills/j-uncharted/scripts/run-engine.sh +36 -2
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +3 -4
- package/skills/jenga/SKILL.md +69 -8
- package/skills/jenga/playbooks/schema.json +4 -4
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-playbooks.sh +289 -4
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- package/templates/permission-levels/level-1-locked.json +1 -1
- package/templates/permission-levels/level-2-guarded.json +1 -1
- package/templates/permission-levels/level-3-standard.json +1 -1
- package/templates/permission-levels/level-4-elevated.json +1 -1
- package/templates/permission-levels/level-5-unrestricted.json +1 -1
- package/templates/playbook-types.json +6 -0
- package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
- package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
- package/scripts/audit-twin-divergence.sh +0 -693
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Jenga AI Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-C3oiuli_.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BADc5mmH.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -90,7 +90,37 @@ resolve_project_dir() {
|
|
|
90
90
|
# Absolute path to this script itself, so the lock-holding re-invocation
|
|
91
91
|
# below works regardless of how this script was originally invoked (a
|
|
92
92
|
# relative path, a PATH lookup, etc.) and regardless of cwd.
|
|
93
|
-
|
|
93
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
94
|
+
SCRIPT_ABS_PATH="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")"
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Resolve with-lock.sh.
|
|
98
|
+
#
|
|
99
|
+
# scripts/postinstall.js mirrors only skills/ and agents/ into a consumer's
|
|
100
|
+
# .claude/ and .agents/ — scripts/ (which owns with-lock.sh) is never copied
|
|
101
|
+
# there, so a consumer install has no <consumer>/scripts/with-lock.sh at all.
|
|
102
|
+
# Probing "$PROJECT_DIR/scripts/with-lock.sh" alone therefore hard-fails every
|
|
103
|
+
# consumer install, even though the file ships fine inside the package.
|
|
104
|
+
#
|
|
105
|
+
# The sibling probe is what fixes that: with-lock.sh always lands in the same
|
|
106
|
+
# directory as this script, in a monorepo checkout AND inside the installed
|
|
107
|
+
# npm package. The remaining two tiers mirror the three-tier shape already
|
|
108
|
+
# used by skills/j-uncharted/scripts/elicitation-state.sh.
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
resolve_with_lock() {
|
|
111
|
+
local candidate
|
|
112
|
+
for candidate in \
|
|
113
|
+
"$SCRIPT_DIR/with-lock.sh" \
|
|
114
|
+
"$PROJECT_DIR/scripts/with-lock.sh" \
|
|
115
|
+
"$PROJECT_DIR/node_modules/@jenga-ai/agent/scripts/with-lock.sh"
|
|
116
|
+
do
|
|
117
|
+
if [ -f "$candidate" ]; then
|
|
118
|
+
printf '%s\n' "$candidate"
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
94
124
|
|
|
95
125
|
# ---------------------------------------------------------------------------
|
|
96
126
|
# Internal locked-mutation entrypoint. Not intended to be invoked directly —
|
|
@@ -206,13 +236,13 @@ COUNTER_FILE="$PROJECT_DIR/project/queue/concurrency-slots-${SESSION_ID}.json"
|
|
|
206
236
|
CONFIG_FILE="$PROJECT_DIR/project/configs/scope-thresholds.json"
|
|
207
237
|
|
|
208
238
|
command -v jq >/dev/null 2>&1 || die 5 "jq is required but not found on PATH"
|
|
209
|
-
|
|
210
|
-
|| die 5 "
|
|
239
|
+
WITH_LOCK="$(resolve_with_lock)" \
|
|
240
|
+
|| die 5 "with-lock.sh not found (looked in $SCRIPT_DIR, $PROJECT_DIR/scripts, and $PROJECT_DIR/node_modules/@jenga-ai/agent/scripts)"
|
|
211
241
|
|
|
212
242
|
mkdir -p "$PROJECT_DIR/project/queue" 2>/dev/null || true
|
|
213
243
|
|
|
214
244
|
set +e
|
|
215
|
-
"$
|
|
245
|
+
"$WITH_LOCK" "$COUNTER_FILE" -- \
|
|
216
246
|
"$SCRIPT_ABS_PATH" __locked-acquire "$ROLE" "$HOLDER_ID" "$COUNTER_FILE" "$CONFIG_FILE"
|
|
217
247
|
STATUS=$?
|
|
218
248
|
set -e
|
|
@@ -61,7 +61,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
61
61
|
# E50_S14_T02: disabled rather than left as an accepted info-level finding — this
|
|
62
62
|
# script's AC requires a clean default-level shellcheck run, and the sourced file does
|
|
63
63
|
# exist; shellcheck simply will not follow it without -x (same fix already applied to
|
|
64
|
-
# scripts/
|
|
64
|
+
# scripts/build-pages-site.sh).
|
|
65
65
|
# shellcheck source=lib/resolve-project-dir.sh disable=SC1091
|
|
66
66
|
source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
|
|
67
67
|
|
|
@@ -92,8 +92,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
92
92
|
# This task's AC requires a clean default-level shellcheck run; SC1091 is an
|
|
93
93
|
# info-level "did not follow sourced file" finding that would otherwise fail that
|
|
94
94
|
# bar even though the sourced file does exist (see -x). Same fix already applied to
|
|
95
|
-
# scripts/apply-j-prefix.sh
|
|
96
|
-
# scripts/build-pages-site.sh.
|
|
95
|
+
# scripts/apply-j-prefix.sh and scripts/build-pages-site.sh.
|
|
97
96
|
# shellcheck source=lib/resolve-project-dir.sh disable=SC1091
|
|
98
97
|
source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
|
|
99
98
|
|
package/scripts/idea_manager.sh
CHANGED
|
@@ -3,9 +3,24 @@
|
|
|
3
3
|
# Run from the repository root.
|
|
4
4
|
|
|
5
5
|
IDEA_FILE="project/ideas.md"
|
|
6
|
-
TEMPLATE="skills/j-idea/assets/idea_template.md"
|
|
7
6
|
PLANS_DIR="project/documentation/plans"
|
|
8
7
|
|
|
8
|
+
# PACKAGE_ROOT locates the package-shipped idea template (skills/j-idea/assets/).
|
|
9
|
+
# This script always lives at scripts/, one level below the package root, in
|
|
10
|
+
# both layouts:
|
|
11
|
+
# - this monorepo checkout
|
|
12
|
+
# - a consumer install, inside node_modules/@jenga-ai/agent/
|
|
13
|
+
# BASH_SOURCE-derived resolution is exact in both cases (same reasoning as
|
|
14
|
+
# scripts/jenga-permission-level-switch.sh's PACKAGE_ROOT derivation), with a
|
|
15
|
+
# defensive node_modules-relative fallback matching the PKG_ROOT convention in
|
|
16
|
+
# skills/init/scripts/init.sh, in case BASH_SOURCE resolution is ever
|
|
17
|
+
# unavailable (e.g. the script is sourced rather than executed).
|
|
18
|
+
PACKAGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
19
|
+
TEMPLATE="$PACKAGE_ROOT/skills/j-idea/assets/idea_template.md"
|
|
20
|
+
if [ ! -f "$TEMPLATE" ]; then
|
|
21
|
+
TEMPLATE="node_modules/@jenga-ai/agent/skills/j-idea/assets/idea_template.md"
|
|
22
|
+
fi
|
|
23
|
+
|
|
9
24
|
usage() {
|
|
10
25
|
cat >&2 <<EOF
|
|
11
26
|
Usage: $0 <subcommand> [args]
|
|
@@ -53,7 +53,37 @@ resolve_project_dir() {
|
|
|
53
53
|
git rev-parse --show-toplevel 2>/dev/null || pwd
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
57
|
+
SCRIPT_ABS_PATH="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")"
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Resolve with-lock.sh.
|
|
61
|
+
#
|
|
62
|
+
# scripts/postinstall.js mirrors only skills/ and agents/ into a consumer's
|
|
63
|
+
# .claude/ and .agents/ — scripts/ (which owns with-lock.sh) is never copied
|
|
64
|
+
# there, so a consumer install has no <consumer>/scripts/with-lock.sh at all.
|
|
65
|
+
# Probing "$PROJECT_DIR/scripts/with-lock.sh" alone therefore hard-fails every
|
|
66
|
+
# consumer install, even though the file ships fine inside the package.
|
|
67
|
+
#
|
|
68
|
+
# The sibling probe is what fixes that: with-lock.sh always lands in the same
|
|
69
|
+
# directory as this script, in a monorepo checkout AND inside the installed
|
|
70
|
+
# npm package. The remaining two tiers mirror the three-tier shape already
|
|
71
|
+
# used by skills/j-uncharted/scripts/elicitation-state.sh.
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
resolve_with_lock() {
|
|
74
|
+
local candidate
|
|
75
|
+
for candidate in \
|
|
76
|
+
"$SCRIPT_DIR/with-lock.sh" \
|
|
77
|
+
"$PROJECT_DIR/scripts/with-lock.sh" \
|
|
78
|
+
"$PROJECT_DIR/node_modules/@jenga-ai/agent/scripts/with-lock.sh"
|
|
79
|
+
do
|
|
80
|
+
if [ -f "$candidate" ]; then
|
|
81
|
+
printf '%s\n' "$candidate"
|
|
82
|
+
return 0
|
|
83
|
+
fi
|
|
84
|
+
done
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
57
87
|
|
|
58
88
|
# ---------------------------------------------------------------------------
|
|
59
89
|
# Internal locked-mutation entrypoint. Not intended to be invoked directly —
|
|
@@ -113,15 +143,15 @@ PROJECT_DIR="$(resolve_project_dir)"
|
|
|
113
143
|
COUNTER_FILE="$PROJECT_DIR/project/queue/concurrency-slots-${SESSION_ID}.json"
|
|
114
144
|
|
|
115
145
|
command -v jq >/dev/null 2>&1 || die 5 "jq is required but not found on PATH"
|
|
116
|
-
|
|
117
|
-
|| die 5 "
|
|
146
|
+
WITH_LOCK="$(resolve_with_lock)" \
|
|
147
|
+
|| die 5 "with-lock.sh not found (looked in $SCRIPT_DIR, $PROJECT_DIR/scripts, and $PROJECT_DIR/node_modules/@jenga-ai/agent/scripts)"
|
|
118
148
|
|
|
119
149
|
# No counter file at all yet: nothing to release, idempotent no-op, no lock
|
|
120
150
|
# needed.
|
|
121
151
|
[ -f "$COUNTER_FILE" ] || exit 0
|
|
122
152
|
|
|
123
153
|
set +e
|
|
124
|
-
"$
|
|
154
|
+
"$WITH_LOCK" "$COUNTER_FILE" -- \
|
|
125
155
|
"$SCRIPT_ABS_PATH" __locked-release "$ROLE" "$HOLDER_ID" "$COUNTER_FILE"
|
|
126
156
|
STATUS=$?
|
|
127
157
|
set -e
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# scripts/render-ranked-list.sh
|
|
4
|
+
#
|
|
5
|
+
# Shared implementation of /dooo's step 2 (identify parallelisable
|
|
6
|
+
# stories/tasks against project/todo.md) and step 3 (render them as a
|
|
7
|
+
# numbered list). Extracted per E63_S01_T01 so this logic exists exactly
|
|
8
|
+
# once — /dooo calls this script instead of describing the scan/render as
|
|
9
|
+
# inline prose, and a sibling task (E63_S01_T03, not yet started) wires
|
|
10
|
+
# `/todo --ranked-list` onto the same script rather than writing a second,
|
|
11
|
+
# independent copy of the eligibility scan.
|
|
12
|
+
#
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# THE ELIGIBILITY RULE (verbatim from /dooo's prior prose — do not alter here
|
|
15
|
+
# without updating the source-of-truth prose in the parent story/task board
|
|
16
|
+
# files first; this script is a faithful extraction, not a redesign)
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# A story or task is eligible if ALL of:
|
|
19
|
+
# - its status is exactly `Pending`
|
|
20
|
+
# - it has no unresolved dependencies: every ID listed in its `depends_on`
|
|
21
|
+
# frontmatter field resolves to a board file whose own `status` is one of
|
|
22
|
+
# `Running`, `In Progress`, or `Passed`. The pre-extraction SKILL.md prose
|
|
23
|
+
# literally named only `Running`/`Passed`; `In Progress` is added here
|
|
24
|
+
# per this task's own board-file description of the rule (which paired
|
|
25
|
+
# `Running`/`In Progress` as the two items' in-work labels) and because no
|
|
26
|
+
# board file has ever actually carried `status: Running` (the schema's
|
|
27
|
+
# real in-work value is `In Progress` — see
|
|
28
|
+
# templates/SCRUM_BOARD_SCHEMA.md's Status Values table), so this is a
|
|
29
|
+
# faithful reproduction of the rule's real-world effect, not a behavior
|
|
30
|
+
# change. This three-value set otherwise still excludes `Passed with
|
|
31
|
+
# remarks`, `Merged`, and `Done`, same as the original prose.
|
|
32
|
+
# - it is directly listed in project/todo.md's real entries, OR (for tasks)
|
|
33
|
+
# its parent story is listed there
|
|
34
|
+
#
|
|
35
|
+
# Candidates are read from two directories: project/board/stories/*.md and
|
|
36
|
+
# project/board/tasks/*.md. A third directory, project/board/epics/*.md, is
|
|
37
|
+
# read only to resolve the status of a dependency that happens to be an
|
|
38
|
+
# epic ID.
|
|
39
|
+
#
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# OUTPUT SHAPE — binding
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# 1-indexed "<id> — <title>" lines, one per eligible item, stories first then
|
|
44
|
+
# tasks, each group in deterministic (sorted) board-file order. No trailing
|
|
45
|
+
# "Done" option, no prompt text — that is /dooo's own interactive framing,
|
|
46
|
+
# added on top of this script's output, not part of it.
|
|
47
|
+
#
|
|
48
|
+
# Fully deterministic: identical board/todo.md state produces byte-identical
|
|
49
|
+
# output across runs. No clock, no randomness, no filesystem-order dependence
|
|
50
|
+
# (directory reads are sorted under LC_ALL=C).
|
|
51
|
+
#
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# USAGE
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# scripts/render-ranked-list.sh
|
|
56
|
+
#
|
|
57
|
+
# Assumes CWD is the project root, matching scripts/board_resolver.sh and
|
|
58
|
+
# scripts/todo_manager.sh's existing convention. Paths may be overridden via
|
|
59
|
+
# environment variables (for test fixtures, or for callers rooted somewhere
|
|
60
|
+
# other than the CWD), in the style of validate-typed-object.sh's
|
|
61
|
+
# JENGA_PLAYBOOK_TYPES_FILE hook:
|
|
62
|
+
#
|
|
63
|
+
# JENGA_TODO_FILE Default: project/todo.md
|
|
64
|
+
# JENGA_BOARD_STORIES_DIR Default: project/board/stories
|
|
65
|
+
# JENGA_BOARD_TASKS_DIR Default: project/board/tasks
|
|
66
|
+
# JENGA_BOARD_EPICS_DIR Default: project/board/epics
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
set -euo pipefail
|
|
70
|
+
export LC_ALL=C
|
|
71
|
+
|
|
72
|
+
TODO_FILE="${JENGA_TODO_FILE:-project/todo.md}"
|
|
73
|
+
STORIES_DIR="${JENGA_BOARD_STORIES_DIR:-project/board/stories}"
|
|
74
|
+
TASKS_DIR="${JENGA_BOARD_TASKS_DIR:-project/board/tasks}"
|
|
75
|
+
EPICS_DIR="${JENGA_BOARD_EPICS_DIR:-project/board/epics}"
|
|
76
|
+
|
|
77
|
+
# The literal, verbatim-preserved "resolved" status set for a dependency.
|
|
78
|
+
RESOLVED_STATUSES="Running
|
|
79
|
+
In Progress
|
|
80
|
+
Passed"
|
|
81
|
+
|
|
82
|
+
is_resolved_status() {
|
|
83
|
+
local status="$1"
|
|
84
|
+
local s
|
|
85
|
+
while IFS= read -r s; do
|
|
86
|
+
if [ "$status" = "$s" ]; then
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
done <<EOF
|
|
90
|
+
$RESOLVED_STATUSES
|
|
91
|
+
EOF
|
|
92
|
+
return 1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Frontmatter helpers
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
# frontmatter_values <file> <field>
|
|
100
|
+
# Prints each value for <field> on its own line: a single line for an inline
|
|
101
|
+
# scalar (`field: value`), or one line per `- item` for a YAML block list
|
|
102
|
+
# (`field:` with nothing after the colon, followed by indented `- ` lines).
|
|
103
|
+
# Prints nothing if the field is absent or has an empty/None-ish value.
|
|
104
|
+
frontmatter_values() {
|
|
105
|
+
local file="$1" field="$2"
|
|
106
|
+
awk -v f="$field" '
|
|
107
|
+
BEGIN { infm = 0; found = 0 }
|
|
108
|
+
/^---[[:space:]]*$/ {
|
|
109
|
+
if (infm == 0) { infm = 1; next } else { exit }
|
|
110
|
+
}
|
|
111
|
+
infm == 1 {
|
|
112
|
+
if (found == 1) {
|
|
113
|
+
if ($0 ~ /^[[:space:]]*-[[:space:]]*/) {
|
|
114
|
+
line = $0
|
|
115
|
+
sub(/^[[:space:]]*-[[:space:]]*/, "", line)
|
|
116
|
+
sub(/[[:space:]]*$/, "", line)
|
|
117
|
+
print line
|
|
118
|
+
next
|
|
119
|
+
} else {
|
|
120
|
+
exit
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if ($0 ~ "^" f ":[[:space:]]*$") { found = 1; next }
|
|
124
|
+
if ($0 ~ "^" f ":[[:space:]]") {
|
|
125
|
+
line = $0
|
|
126
|
+
sub("^" f ":[[:space:]]*", "", line)
|
|
127
|
+
sub(/[[:space:]]*$/, "", line)
|
|
128
|
+
print line
|
|
129
|
+
exit
|
|
130
|
+
}
|
|
131
|
+
if ($0 ~ "^" f ":$") { exit }
|
|
132
|
+
}
|
|
133
|
+
' "$file"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
frontmatter_scalar() {
|
|
137
|
+
frontmatter_values "$1" "$2" | head -n1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# ID → status resolution
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
# id_dir <id> — picks the board directory an ID belongs to, by shape:
|
|
145
|
+
# 0 underscores -> epic, 1 -> story, 2 -> task.
|
|
146
|
+
id_dir() {
|
|
147
|
+
local id="$1"
|
|
148
|
+
local underscores
|
|
149
|
+
underscores=$(printf '%s' "$id" | tr -cd '_' | wc -c | tr -d ' ')
|
|
150
|
+
case "$underscores" in
|
|
151
|
+
0) printf '%s' "$EPICS_DIR" ;;
|
|
152
|
+
1) printf '%s' "$STORIES_DIR" ;;
|
|
153
|
+
*) printf '%s' "$TASKS_DIR" ;;
|
|
154
|
+
esac
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# status_of <id> — prints the status of the board file matching <id>, or
|
|
158
|
+
# nothing if no such file exists (a dangling/unresolvable reference).
|
|
159
|
+
status_of() {
|
|
160
|
+
local id="$1"
|
|
161
|
+
local dir file
|
|
162
|
+
dir=$(id_dir "$id")
|
|
163
|
+
for file in "$dir"/"$id"_*.md; do
|
|
164
|
+
[ -e "$file" ] || continue
|
|
165
|
+
frontmatter_scalar "$file" "status"
|
|
166
|
+
return 0
|
|
167
|
+
done
|
|
168
|
+
return 0
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# deps_resolved <file> — true (exit 0) if every depends_on ID resolves to a
|
|
172
|
+
# board file whose status is in RESOLVED_STATUSES. No depends_on, or only
|
|
173
|
+
# empty/None-ish entries, counts as resolved.
|
|
174
|
+
deps_resolved() {
|
|
175
|
+
local file="$1"
|
|
176
|
+
local dep status
|
|
177
|
+
while IFS= read -r dep; do
|
|
178
|
+
[ -z "$dep" ] && continue
|
|
179
|
+
case "$dep" in
|
|
180
|
+
[Nn]one) continue ;;
|
|
181
|
+
esac
|
|
182
|
+
status=$(status_of "$dep")
|
|
183
|
+
if [ -z "$status" ] || ! is_resolved_status "$status"; then
|
|
184
|
+
return 1
|
|
185
|
+
fi
|
|
186
|
+
done < <(frontmatter_values "$file" "depends_on")
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# todo.md real-entries + listing check
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
# Mirrors todo_manager.sh's real_entries() filter (blank lines, `#` comments,
|
|
195
|
+
# HTML `<!-- -->` comments stripped). Duplicated in miniature rather than
|
|
196
|
+
# shelled out to todo_manager.sh, since that script hardcodes its own
|
|
197
|
+
# project/todo.md path with no override hook and this script must also run
|
|
198
|
+
# against test fixtures and arbitrary project roots.
|
|
199
|
+
TODO_REAL_ENTRIES=""
|
|
200
|
+
if [ -f "$TODO_FILE" ]; then
|
|
201
|
+
TODO_REAL_ENTRIES=$(grep -v '^[[:space:]]*$' "$TODO_FILE" 2>/dev/null \
|
|
202
|
+
| grep -v '^[[:space:]]*#' \
|
|
203
|
+
| grep -v '^[[:space:]]*<!--' || true)
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# todo_lists <id> — true if <id> appears, word-bounded, in a real todo.md
|
|
207
|
+
# entry line.
|
|
208
|
+
todo_lists() {
|
|
209
|
+
local id="$1"
|
|
210
|
+
[ -z "$TODO_REAL_ENTRIES" ] && return 1
|
|
211
|
+
printf '%s\n' "$TODO_REAL_ENTRIES" \
|
|
212
|
+
| grep -Eq "(^|[^A-Za-z0-9_])${id}([^A-Za-z0-9_]|\$)"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
# Eligibility scan
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
ELIGIBLE=""
|
|
220
|
+
|
|
221
|
+
add_eligible() {
|
|
222
|
+
local id="$1" title="$2"
|
|
223
|
+
ELIGIBLE="${ELIGIBLE}${id}"$'\t'"${title}"$'\n'
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
scan_stories() {
|
|
227
|
+
local file id status title
|
|
228
|
+
for file in $(find "$STORIES_DIR" -maxdepth 1 -name '*.md' 2>/dev/null | sort); do
|
|
229
|
+
id=$(frontmatter_scalar "$file" "id")
|
|
230
|
+
[ -z "$id" ] && continue
|
|
231
|
+
status=$(frontmatter_scalar "$file" "status")
|
|
232
|
+
[ "$status" = "Pending" ] || continue
|
|
233
|
+
deps_resolved "$file" || continue
|
|
234
|
+
todo_lists "$id" || continue
|
|
235
|
+
title=$(frontmatter_scalar "$file" "title")
|
|
236
|
+
add_eligible "$id" "$title"
|
|
237
|
+
done
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
scan_tasks() {
|
|
241
|
+
local file id story_id status title
|
|
242
|
+
for file in $(find "$TASKS_DIR" -maxdepth 1 -name '*.md' 2>/dev/null | sort); do
|
|
243
|
+
id=$(frontmatter_scalar "$file" "id")
|
|
244
|
+
[ -z "$id" ] && continue
|
|
245
|
+
status=$(frontmatter_scalar "$file" "status")
|
|
246
|
+
[ "$status" = "Pending" ] || continue
|
|
247
|
+
deps_resolved "$file" || continue
|
|
248
|
+
story_id=$(frontmatter_scalar "$file" "story_id")
|
|
249
|
+
if todo_lists "$id" || { [ -n "$story_id" ] && todo_lists "$story_id"; }; then
|
|
250
|
+
title=$(frontmatter_scalar "$file" "title")
|
|
251
|
+
add_eligible "$id" "$title"
|
|
252
|
+
fi
|
|
253
|
+
done
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
scan_stories
|
|
257
|
+
scan_tasks
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# Render
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
n=0
|
|
264
|
+
while IFS=$'\t' read -r id title; do
|
|
265
|
+
[ -z "$id" ] && continue
|
|
266
|
+
n=$((n + 1))
|
|
267
|
+
printf '%d. %s — %s\n' "$n" "$id" "$title"
|
|
268
|
+
done <<EOF
|
|
269
|
+
$ELIGIBLE
|
|
270
|
+
EOF
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
E50_S22_T03 — Repoint the remaining dead bare skills/<name>/ prose references and
|
|
4
|
+
fix two stale scripts/generate-j-alias.sh comments in mirror.sh.
|
|
5
|
+
|
|
6
|
+
Context: the story's Findings table counted 81 dead bare-skills/<name>/ references
|
|
7
|
+
across skills/j-*/SKILL.md bodies. T01 (frontmatter) and T02 (body preamble
|
|
8
|
+
rewrite) already resolved 79 of those 81 as a side effect of rewriting the exact
|
|
9
|
+
text blocks that contained them — T02's replacement text reintroduces one bare
|
|
10
|
+
reference per file, but explicitly in past tense ("was previously generated from
|
|
11
|
+
a bare `skills/<name>/SKILL.md` source; `E50_S15` deleted that directory"), which
|
|
12
|
+
is deliberate historical narration this task's own AC requires preserving.
|
|
13
|
+
|
|
14
|
+
Of the remaining references, only two required an actual fix:
|
|
15
|
+
1. skills/j-do/SKILL.md's illustrative example "(e.g. `skills/do/SKILL.md`)" —
|
|
16
|
+
not historical narration, just an example that should use the current path.
|
|
17
|
+
2. Two comments in skills/j-mirror-public/scripts/mirror.sh (L1363, L1523) that
|
|
18
|
+
still describe scripts/generate-j-alias.sh as a live generator.
|
|
19
|
+
|
|
20
|
+
Both are exact string replacements, verified present verbatim before substitution
|
|
21
|
+
so a mismatch fails loudly rather than silently no-op'ing.
|
|
22
|
+
|
|
23
|
+
Usage: scripts/repoint-dead-bare-path-prose.py [--dry-run]
|
|
24
|
+
Run from the repository root.
|
|
25
|
+
"""
|
|
26
|
+
import sys
|
|
27
|
+
|
|
28
|
+
REPLACEMENTS = [
|
|
29
|
+
(
|
|
30
|
+
"skills/j-do/SKILL.md",
|
|
31
|
+
"(e.g. `skills/do/SKILL.md`).",
|
|
32
|
+
"(e.g. `skills/j-do/SKILL.md`).",
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
"skills/j-mirror-public/scripts/mirror.sh",
|
|
36
|
+
"# skills/j-<name>/ twins are generated (scripts/generate-j-alias.sh) with",
|
|
37
|
+
"# skills/j-<name>/ twins were generated (scripts/generate-j-alias.sh, retired by E50_S14) with",
|
|
38
|
+
),
|
|
39
|
+
(
|
|
40
|
+
"skills/j-mirror-public/scripts/mirror.sh",
|
|
41
|
+
'log "orphaned-twin rewrite: frontmatter scripts/generate-j-alias.sh currently writes."',
|
|
42
|
+
'log "orphaned-twin rewrite: standard hand-authored frontmatter shape (scripts/generate-j-alias.sh was retired by E50_S14)."',
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
dry_run = "--dry-run" in sys.argv
|
|
49
|
+
changed = []
|
|
50
|
+
|
|
51
|
+
for path, old, new in REPLACEMENTS:
|
|
52
|
+
with open(path, encoding="utf-8") as f:
|
|
53
|
+
content = f.read()
|
|
54
|
+
count = content.count(old)
|
|
55
|
+
if count == 0:
|
|
56
|
+
print(f"ERROR: expected text not found verbatim in {path}:", file=sys.stderr)
|
|
57
|
+
print(repr(old), file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
if count > 1:
|
|
60
|
+
print(
|
|
61
|
+
f"ERROR: expected text matched {count} times in {path} (expected exactly 1) — "
|
|
62
|
+
"refusing to guess which one:",
|
|
63
|
+
file=sys.stderr,
|
|
64
|
+
)
|
|
65
|
+
print(repr(old), file=sys.stderr)
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
if old == new:
|
|
68
|
+
continue
|
|
69
|
+
new_content = content.replace(old, new, 1)
|
|
70
|
+
if not dry_run:
|
|
71
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
72
|
+
f.write(new_content)
|
|
73
|
+
changed.append(path)
|
|
74
|
+
|
|
75
|
+
print(f"Changed {len(changed)} file(s):")
|
|
76
|
+
for p in changed:
|
|
77
|
+
print(f" {p}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|