@jenga-ai/agent 3.6.0 → 4.1.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.
Files changed (86) hide show
  1. package/README.md +32 -1
  2. package/lib/generate-skill-allow-list.js +42 -5
  3. package/lib/skill-allow-list.json +1 -1
  4. package/package.json +1 -2
  5. package/project/app/api/lib/resolve-project-root.js +1 -1
  6. package/project/app/api/scripts/capture-snapshot.js +12 -7
  7. package/project/app/package.json +4 -0
  8. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  9. package/project/app/ui/dist/assets/index-DX2pfTAW.js +104 -0
  10. package/project/app/ui/dist/index.html +2 -2
  11. package/scripts/acquire-concurrency-slot.sh +34 -4
  12. package/scripts/apply-j-prefix.sh +1 -1
  13. package/scripts/delete-bare-skill-dirs.sh +1 -2
  14. package/scripts/idea_manager.sh +16 -1
  15. package/scripts/release-concurrency-slot.sh +34 -4
  16. package/scripts/render-ranked-list.sh +270 -0
  17. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  18. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  19. package/scripts/strip-polyfill-frontmatter.py +166 -0
  20. package/scripts/todo_manager.sh +16 -1
  21. package/scripts/validate-typed-object.sh +750 -0
  22. package/scripts/verify-postinstall-reconcile.sh +33 -7
  23. package/skills/index/scripts/board_index.py +29 -1
  24. package/skills/j-brainstorm/SKILL.md +3 -4
  25. package/skills/j-btw/SKILL.md +3 -4
  26. package/skills/j-clearify/SKILL.md +3 -4
  27. package/skills/j-close-story/SKILL.md +11 -12
  28. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  29. package/skills/j-commit/SKILL.md +3 -4
  30. package/skills/j-continue/SKILL.md +5 -6
  31. package/skills/j-deep-dive/SKILL.md +3 -4
  32. package/skills/j-distribute/SKILL.md +3 -4
  33. package/skills/j-do/SKILL.md +13 -15
  34. package/skills/j-doc/SKILL.md +3 -4
  35. package/skills/j-doc/scripts/resolve_last_update.py +27 -1
  36. package/skills/j-doc-sync/SKILL.md +3 -4
  37. package/skills/j-dooo/SKILL.md +6 -15
  38. package/skills/j-error/SKILL.md +3 -4
  39. package/skills/j-evaluate/SKILL.md +3 -4
  40. package/skills/j-examplify/SKILL.md +3 -4
  41. package/skills/j-help/SKILL.md +3 -4
  42. package/skills/j-idea/SKILL.md +3 -4
  43. package/skills/j-improve/SKILL.md +3 -4
  44. package/skills/j-init/SKILL.md +20 -11
  45. package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
  46. package/skills/j-jbp/SKILL.md +3 -4
  47. package/skills/j-lgtm/SKILL.md +3 -4
  48. package/skills/j-pi-plan/SKILL.md +3 -4
  49. package/skills/j-proceed/SKILL.md +3 -4
  50. package/skills/j-publish/SKILL.md +3 -4
  51. package/skills/j-publish/scripts/run_gates.sh +1 -1
  52. package/skills/j-reconcile/SKILL.md +38 -5
  53. package/skills/j-reconcile/assets/report_format.md +11 -0
  54. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
  55. package/skills/j-reconcile-origin/SKILL.md +3 -4
  56. package/skills/j-redo/SKILL.md +3 -4
  57. package/skills/j-skillify/SKILL.md +3 -4
  58. package/skills/j-spinoff/SKILL.md +3 -4
  59. package/skills/j-status/SKILL.md +4 -5
  60. package/skills/j-todo/SKILL.md +42 -5
  61. package/skills/j-todo/scripts/add_trivial_task.sh +12 -1
  62. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  63. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  64. package/skills/j-uncharted/SKILL.md +251 -12
  65. package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
  66. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  67. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  68. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  69. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  70. package/skills/j-wtf/SKILL.md +3 -4
  71. package/skills/jenga/SKILL.md +106 -10
  72. package/skills/jenga/playbooks/schema.json +4 -4
  73. package/skills/jenga/scripts/enrich-nl-prompt.sh +225 -0
  74. package/skills/jenga/scripts/load-nl-catalog.js +6 -3
  75. package/skills/jenga/scripts/load-playbooks.sh +289 -4
  76. package/skills/jenga/scripts/match-playbook.sh +6 -5
  77. package/skills/jenga/scripts/run-playbook-step.sh +270 -3
  78. package/templates/permission-levels/level-1-locked.json +1 -1
  79. package/templates/permission-levels/level-2-guarded.json +1 -1
  80. package/templates/permission-levels/level-3-standard.json +1 -1
  81. package/templates/permission-levels/level-4-elevated.json +1 -1
  82. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  83. package/templates/playbook-types.json +6 -0
  84. package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
  85. package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
  86. package/scripts/audit-twin-divergence.sh +0 -693
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ E50_S22_T02 — Rewrite the 37 stale generator-era body preambles to the canonical
4
+ hand-edited framing.
5
+
6
+ 37 of the skills/j-*/SKILL.md files open their body (directly under the H1) with a
7
+ two-paragraph preamble describing the retired twin-generation era: a bare
8
+ skills/<name>/ "source" this file was supposedly generated from, and a warning to
9
+ re-run scripts/generate-j-alias.sh instead of hand-editing. Both the bare
10
+ directories (E50_S15) and the generator itself (E50_S14) are gone.
11
+
12
+ This script:
13
+ 1. Replaces that two-paragraph block, per file, with the canonical/hand-edited
14
+ framing already used by skills/j-do/SKILL.md and skills/j-dev-done/SKILL.md,
15
+ name-substituted.
16
+ 2. Corrects skills/j-do/SKILL.md's and skills/j-dev-done/SKILL.md's own existing
17
+ warning text, which predates E50_S15 landing and still claims a bare copy is
18
+ "awaiting deletion" and warns against running a generator that no longer
19
+ exists at all.
20
+ 3. Rewrites j-dev-done's "Known divergence" paragraph, which asserted (future
21
+ tense) that the bare /dev-done form "still exhibits" old behavior "until
22
+ E50_S15 deletes it" — E50_S15 already landed, so this is restated as a past-
23
+ tense historical note.
24
+
25
+ Idempotent and safe to re-run (already-corrected files simply won't match and are
26
+ skipped). Fails loudly (non-zero exit) if a file containing the trigger phrase
27
+ "literal-directory-name duplicate" doesn't fit the fully-verified-uniform pattern,
28
+ rather than silently leaving it unrewritten.
29
+
30
+ Usage: scripts/rewrite-stale-skill-preambles.py [--dry-run]
31
+ Run from the repository root.
32
+ """
33
+ import glob
34
+ import re
35
+ import sys
36
+
37
+ TRIGGER = "literal-directory-name duplicate"
38
+
39
+ STALE_PREAMBLE_RE = re.compile(
40
+ r"This skill is a literal-directory-name duplicate of `skills/(?P<name>[a-z0-9-]+)/`\. "
41
+ r"It exists so that `/j-(?P=name)` \(and `j\.j-(?P=name)`\) give a guaranteed-unshadowed "
42
+ r"way to reach the same flow as `/(?P=name)`, even if a host tool's own built-in command "
43
+ r"of the same name would otherwise shadow or override the bare `/(?P=name)` alias "
44
+ r"\(Claude Code's native skill resolution is a literal-string, directory-name-based match "
45
+ r"— see `docs/skill-authoring\.md`'s \"Invocation Convention\"\)\.\n\n"
46
+ r"This file is generated/synced by `scripts/generate-j-alias\.sh (?P=name)` from "
47
+ r"`skills/(?P=name)/SKILL\.md` — do not hand-edit it; re-run the generator instead to "
48
+ r"pick up source changes\."
49
+ )
50
+
51
+
52
+ def canonical_replacement(name):
53
+ return (
54
+ f"`skills/j-{name}/` is the **canonical, hand-edited** directory for this skill, per "
55
+ "CLAUDE.md's \"The Canonical Naming Contract\" (the `E50` reopening of 2026-09-09, "
56
+ f"which promoted `skills/j-{name}/` from generated twin to sole canonical form). The "
57
+ "`j-` prefix is there for collision safety — a real directory under a distinct name, so "
58
+ "a host tool shipping its own same-named built-in command cannot shadow it (Claude "
59
+ "Code's native skill resolution is a literal-string, directory-name-based match; see "
60
+ "`docs/skill-authoring.md`'s \"Invocation Convention\").\n\n"
61
+ "> ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — "
62
+ "there is nothing to run.** This file was previously generated from a bare "
63
+ f"`skills/{name}/SKILL.md` source; `E50_S15` deleted that directory. This file is now "
64
+ "the sole canonical, hand-edited source for this skill — edit it directly."
65
+ )
66
+
67
+
68
+ # --- j-do and j-dev-done: targeted corrections to their already-correct-model text ---
69
+
70
+ J_DO_OLD = (
71
+ "> ⚠️ **Do not run `scripts/generate-j-alias.sh do` against this directory.** This file was\n"
72
+ "> previously generated from `skills/do/SKILL.md`, and carried a banner saying so. That relationship\n"
73
+ "> is inverted under the contract above: edits land here first, and `skills/do/` is the copy awaiting\n"
74
+ "> deletion by `E50_S15`. Regenerating would overwrite this file from the stale bare directory.\n"
75
+ "> CLAUDE.md states the same prohibition in general terms; this is the concrete instance of it."
76
+ )
77
+ J_DO_NEW = (
78
+ "> ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — there is\n"
79
+ "> nothing to run.** This file was previously generated from a bare `skills/do/SKILL.md` source;\n"
80
+ "> `E50_S15` deleted that directory. This file is now the sole canonical, hand-edited source for\n"
81
+ "> this skill — edit it directly."
82
+ )
83
+
84
+ J_DEV_DONE_OLD_WARNING = (
85
+ "> ⚠️ **Do not run `scripts/generate-j-alias.sh dev-done` against this directory.** This file was\n"
86
+ "> previously generated from `skills/dev-done/SKILL.md`, and carried a banner saying so. That\n"
87
+ "> relationship is inverted under the contract above: edits land here first, and `skills/dev-done/` is\n"
88
+ "> the copy awaiting deletion by `E50_S15`. Regenerating would overwrite this file from the stale bare\n"
89
+ "> directory and silently drop the post-sync commit step below. CLAUDE.md states the same prohibition\n"
90
+ "> in general terms; this is the concrete instance of it."
91
+ )
92
+ J_DEV_DONE_NEW_WARNING = (
93
+ "> ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — there is\n"
94
+ "> nothing to run.** This file was previously generated from a bare `skills/dev-done/SKILL.md`\n"
95
+ "> source; `E50_S15` deleted that directory. This file is now the sole canonical, hand-edited\n"
96
+ "> source for this skill — edit it directly."
97
+ )
98
+
99
+ J_DEV_DONE_OLD_DIVERGENCE = (
100
+ "> **Known divergence:** `skills/dev-done/` does **not** carry the post-sync commit step. Until\n"
101
+ "> `E50_S15` deletes it, the bare `/dev-done` form still exhibits the original\n"
102
+ "> leaves-the-tree-dirty behavior. Invoke `/j-dev-done` to get the fix."
103
+ )
104
+ J_DEV_DONE_NEW_DIVERGENCE = (
105
+ "> **Historical note:** before `E50_S15` deleted the bare `skills/dev-done/` directory, that\n"
106
+ "> copy did not carry the post-sync commit step below — this canonical file was already ahead\n"
107
+ "> of it. That divergence is now moot; there is no bare form left to diverge from."
108
+ )
109
+
110
+
111
+ def process_generic_file(path, dry_run=False):
112
+ with open(path, encoding="utf-8") as f:
113
+ content = f.read()
114
+ if TRIGGER not in content:
115
+ return None
116
+ match = STALE_PREAMBLE_RE.search(content)
117
+ if not match:
118
+ return "unmatched"
119
+ name = match.group("name")
120
+ new_content = content[: match.start()] + canonical_replacement(name) + content[match.end() :]
121
+ if not dry_run:
122
+ with open(path, "w", encoding="utf-8") as f:
123
+ f.write(new_content)
124
+ return True
125
+
126
+
127
+ def process_special_file(path, old_new_pairs, dry_run=False):
128
+ with open(path, encoding="utf-8") as f:
129
+ content = f.read()
130
+ changed = False
131
+ for old, new in old_new_pairs:
132
+ if old not in content:
133
+ print(f"ERROR: expected text not found verbatim in {path}:", file=sys.stderr)
134
+ print(repr(old), file=sys.stderr)
135
+ sys.exit(1)
136
+ content = content.replace(old, new, 1)
137
+ changed = True
138
+ if changed and not dry_run:
139
+ with open(path, "w", encoding="utf-8") as f:
140
+ f.write(content)
141
+ return changed
142
+
143
+
144
+ def main():
145
+ dry_run = "--dry-run" in sys.argv
146
+ files = sorted(glob.glob("skills/j-*/SKILL.md"))
147
+ changed_files = []
148
+ unmatched = []
149
+
150
+ for path in files:
151
+ if path in ("skills/j-do/SKILL.md", "skills/j-dev-done/SKILL.md"):
152
+ continue
153
+ result = process_generic_file(path, dry_run=dry_run)
154
+ if result == "unmatched":
155
+ unmatched.append(path)
156
+ elif result:
157
+ changed_files.append(path)
158
+
159
+ if process_special_file("skills/j-do/SKILL.md", [(J_DO_OLD, J_DO_NEW)], dry_run=dry_run):
160
+ changed_files.append("skills/j-do/SKILL.md")
161
+
162
+ if process_special_file(
163
+ "skills/j-dev-done/SKILL.md",
164
+ [
165
+ (J_DEV_DONE_OLD_WARNING, J_DEV_DONE_NEW_WARNING),
166
+ (J_DEV_DONE_OLD_DIVERGENCE, J_DEV_DONE_NEW_DIVERGENCE),
167
+ ],
168
+ dry_run=dry_run,
169
+ ):
170
+ changed_files.append("skills/j-dev-done/SKILL.md")
171
+
172
+ print(f"Changed {len(changed_files)} file(s):")
173
+ for p in sorted(changed_files):
174
+ print(f" {p}")
175
+
176
+ if unmatched:
177
+ print(
178
+ f"\nERROR: {len(unmatched)} file(s) contain the trigger phrase but did not match the "
179
+ "expected preamble pattern — investigate manually:",
180
+ file=sys.stderr,
181
+ )
182
+ for p in unmatched:
183
+ print(f" {p}", file=sys.stderr)
184
+ sys.exit(1)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ E50_S22_T01 — Strip retired-era polyfill boilerplate from j-* skill frontmatter.
4
+
5
+ For every skills/j-*/SKILL.md file (excluding the 6 natively-authored skills, which
6
+ never carried this boilerplate), this script:
7
+
8
+ 1. Strips the leading prefix "Polyfill alias of the <name> skill under a
9
+ collision-safe directory name. Identical behavior to /<name> — " from the
10
+ frontmatter `description:` field, preserving everything after it verbatim.
11
+ 2. Strips the trailing generic clause "Use when the bare /<name> form is shadowed
12
+ by another tool's own built-in command of the same name." if present verbatim.
13
+ (skills/j-init/SKILL.md carries a different, substantive custom clause about a
14
+ real GitHub Copilot collision — that one is intentionally left untouched.)
15
+ 3. Special-cases skills/j-idea/SKILL.md: also strips the leftover E50_S21
16
+ "DEPRECATED - use the prefixed 'j-idea' skill instead. " clause.
17
+ 4. Removes any " - polyfill" line from the frontmatter `keywords:` block.
18
+
19
+ Idempotent and safe to re-run. Reports every file it changes and fails loudly
20
+ (non-zero exit) if a file matching the target set doesn't fit the expected pattern,
21
+ rather than silently skipping it.
22
+
23
+ Usage: scripts/strip-polyfill-frontmatter.py [--dry-run]
24
+ Run from the repository root.
25
+ """
26
+ import glob
27
+ import json
28
+ import re
29
+ import sys
30
+
31
+ try:
32
+ import yaml
33
+ except ImportError:
34
+ yaml = None
35
+
36
+ NATIVE_SKILLS = {
37
+ "j-cloud-connect",
38
+ "j-dashboard",
39
+ "j-dashboard-share",
40
+ "j-gitignore",
41
+ "j-playbook",
42
+ "j-playbook-new",
43
+ }
44
+
45
+ PREFIX_RE = re.compile(
46
+ r"^description: Polyfill alias of the .+? skill under a collision-safe directory name\. "
47
+ r"Identical behavior to /\S+ — "
48
+ )
49
+ GENERIC_SUFFIX_RE = re.compile(
50
+ r" Use when the bare /\S+ form is shadowed by another tool's own built-in command of the same name\.$"
51
+ )
52
+ IDEA_DEPRECATED_RE = re.compile(
53
+ r"^description: DEPRECATED - use the prefixed 'j-idea' skill instead\. "
54
+ )
55
+ # Matches the preceding newline too, so removal doesn't leave a blank line behind
56
+ # (whether "polyfill" sits mid-list or is the last entry right before the closing "---").
57
+ POLYFILL_KEYWORD_RE = re.compile(r"\n[ \t]*-[ \t]*polyfill[ \t]*(?=\n)")
58
+
59
+
60
+ def requote_if_unsafe(line):
61
+ """
62
+ Stripping the polyfill prefix can leave a plain-scalar description value that
63
+ starts with a YAML-special character (e.g. j-deep-dive's substantive text is
64
+ the literal ">.", which YAML reads as an invalid block-scalar indicator once it
65
+ is no longer preceded by other text). If the resulting `description: <value>`
66
+ line doesn't parse as valid YAML, re-render it as a double-quoted (JSON-style,
67
+ which is also valid YAML) scalar instead, preserving the value verbatim.
68
+ """
69
+ if yaml is None or not line.startswith("description: "):
70
+ return line
71
+ value = line[len("description: ") :]
72
+ try:
73
+ yaml.safe_load(line)
74
+ return line
75
+ except yaml.YAMLError:
76
+ return "description: " + json.dumps(value)
77
+
78
+
79
+ def process_file(path, dry_run=False):
80
+ with open(path, encoding="utf-8") as f:
81
+ content = f.read()
82
+
83
+ if not content.startswith("---\n"):
84
+ return None # no frontmatter, nothing to do
85
+ end = content.find("\n---", 4)
86
+ if end == -1:
87
+ return None
88
+ frontmatter = content[: end + 4]
89
+ rest = content[end + 4 :]
90
+
91
+ lines = frontmatter.split("\n")
92
+ changed = False
93
+ desc_found = False
94
+
95
+ for i, line in enumerate(lines):
96
+ if line.startswith("description:"):
97
+ desc_found = True
98
+ original = line
99
+ new_line = PREFIX_RE.sub("description: ", line)
100
+ if new_line == line:
101
+ # Not the polyfill-prefixed shape — leave untouched (native skills).
102
+ continue
103
+ new_line = GENERIC_SUFFIX_RE.sub("", new_line)
104
+ if "/j-idea/" in path or path.endswith("j-idea/SKILL.md"):
105
+ new_line = IDEA_DEPRECATED_RE.sub("description: ", new_line)
106
+ new_line = requote_if_unsafe(new_line)
107
+ if new_line != original:
108
+ lines[i] = new_line
109
+ changed = True
110
+
111
+ new_frontmatter = "\n".join(lines)
112
+
113
+ # Strip " - polyfill" keyword lines (including their preceding newline, so no
114
+ # blank line is left behind regardless of position in the list).
115
+ if POLYFILL_KEYWORD_RE.search(new_frontmatter):
116
+ new_frontmatter = POLYFILL_KEYWORD_RE.sub("", new_frontmatter)
117
+ changed = True
118
+
119
+ if not changed:
120
+ return False
121
+
122
+ new_content = new_frontmatter + rest
123
+ if not dry_run:
124
+ with open(path, "w", encoding="utf-8") as f:
125
+ f.write(new_content)
126
+ return True
127
+
128
+
129
+ def main():
130
+ dry_run = "--dry-run" in sys.argv
131
+ files = sorted(glob.glob("skills/j-*/SKILL.md"))
132
+ changed_files = []
133
+ unmatched_polyfill_prefix = []
134
+
135
+ for path in files:
136
+ skill_dir = path.split("/")[1]
137
+ if skill_dir in NATIVE_SKILLS:
138
+ continue
139
+
140
+ with open(path, encoding="utf-8") as f:
141
+ head = f.read(4000)
142
+ has_prefix = "Polyfill alias of the" in head
143
+
144
+ result = process_file(path, dry_run=dry_run)
145
+ if has_prefix and result is False:
146
+ unmatched_polyfill_prefix.append(path)
147
+ elif result:
148
+ changed_files.append(path)
149
+
150
+ print(f"Changed {len(changed_files)} file(s):")
151
+ for p in changed_files:
152
+ print(f" {p}")
153
+
154
+ if unmatched_polyfill_prefix:
155
+ print(
156
+ f"\nERROR: {len(unmatched_polyfill_prefix)} file(s) contain 'Polyfill alias of the' "
157
+ "but did not match the expected description pattern — investigate manually:",
158
+ file=sys.stderr,
159
+ )
160
+ for p in unmatched_polyfill_prefix:
161
+ print(f" {p}", file=sys.stderr)
162
+ sys.exit(1)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ main()
@@ -3,7 +3,22 @@
3
3
  # Run from the repository root.
4
4
 
5
5
  TODO_FILE="project/todo.md"
6
- TEMPLATE="skills/j-todo/assets/todo_template.md"
6
+
7
+ # PACKAGE_ROOT locates the package-shipped todo template (skills/j-todo/assets/).
8
+ # This script always lives at scripts/, one level below the package root, in
9
+ # both layouts:
10
+ # - this monorepo checkout
11
+ # - a consumer install, inside node_modules/@jenga-ai/agent/
12
+ # BASH_SOURCE-derived resolution is exact in both cases (same reasoning as
13
+ # scripts/jenga-permission-level-switch.sh's PACKAGE_ROOT derivation), with a
14
+ # defensive node_modules-relative fallback matching the PKG_ROOT convention in
15
+ # skills/init/scripts/init.sh, in case BASH_SOURCE resolution is ever
16
+ # unavailable (e.g. the script is sourced rather than executed).
17
+ PACKAGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
18
+ TEMPLATE="$PACKAGE_ROOT/skills/j-todo/assets/todo_template.md"
19
+ if [ ! -f "$TEMPLATE" ]; then
20
+ TEMPLATE="node_modules/@jenga-ai/agent/skills/j-todo/assets/todo_template.md"
21
+ fi
7
22
 
8
23
  usage() {
9
24
  cat >&2 <<EOF