@jenga-ai/agent 1.3.0 → 3.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.
Files changed (175) hide show
  1. package/README.md +93 -256
  2. package/agents/developer.md +9 -8
  3. package/agents/scrum-master.md +57 -23
  4. package/agents/tester.md +51 -5
  5. package/hooks/on_session_end.sh +13 -1
  6. package/lib/generate-agent-context.js +18 -1
  7. package/lib/generate-copilot-instructions.js +18 -1
  8. package/lib/generate-skill-allow-list.js +197 -0
  9. package/lib/skill-allow-list.json +42 -0
  10. package/package.json +17 -13
  11. package/scripts/apply-j-prefix.sh +243 -0
  12. package/scripts/consume-context-digest.sh +103 -0
  13. package/scripts/generate-j-alias.sh +333 -0
  14. package/scripts/postinstall.js +25 -0
  15. package/scripts/sweep-stale-context-digests.sh +132 -0
  16. package/scripts/validate-board.sh +5 -0
  17. package/scripts/write-context-digest.sh +230 -0
  18. package/skills/{brainstorm → j-brainstorm}/SKILL.md +9 -2
  19. package/skills/{btw → j-btw}/SKILL.md +9 -2
  20. package/skills/{clearify → j-clearify}/SKILL.md +9 -2
  21. package/skills/{close-story → j-close-story}/SKILL.md +92 -13
  22. package/skills/j-close-story/scripts/check-privatized.sh +345 -0
  23. package/skills/{close-story → j-close-story}/scripts/check-story-closeable.sh +1 -1
  24. package/skills/{close-story → j-close-story}/scripts/extract-task-diff-stats.sh +1 -1
  25. package/skills/{commit → j-commit}/SKILL.md +9 -2
  26. package/skills/j-continue/SKILL.md +36 -0
  27. package/skills/{deep-dive → j-deep-dive}/SKILL.md +9 -8
  28. package/skills/{dev-done → j-dev-done}/SKILL.md +11 -4
  29. package/skills/{dev-done → j-dev-done}/scripts/classify-commit-outcome.sh +4 -4
  30. package/skills/{distribute → j-distribute}/SKILL.md +17 -10
  31. package/skills/{distribute → j-distribute}/scripts/distribute-changes.sh +1 -1
  32. package/skills/{do → j-do}/SKILL.md +111 -14
  33. package/skills/j-doc/README.md +155 -0
  34. package/skills/{doc → j-doc}/SKILL.md +55 -18
  35. package/skills/j-doc/authoring-notes.md +72 -0
  36. package/skills/j-doc/scripts/resolve_last_update.py +149 -0
  37. package/skills/{doc-sync → j-doc-sync}/SKILL.md +9 -2
  38. package/skills/{dooo → j-dooo}/SKILL.md +9 -2
  39. package/skills/j-error/SKILL.md +36 -0
  40. package/skills/{evaluate → j-evaluate}/SKILL.md +9 -2
  41. package/skills/j-examplify/SKILL.md +49 -0
  42. package/skills/{help → j-help}/SKILL.md +9 -2
  43. package/skills/{idea → j-idea}/SKILL.md +10 -3
  44. package/skills/{idea → j-idea}/assets/idea_handoff_template.md +1 -1
  45. package/skills/{improve → j-improve}/SKILL.md +9 -2
  46. package/skills/{init → j-init}/SKILL.md +21 -8
  47. package/skills/j-init/assets/scope-thresholds_template.json +7 -0
  48. package/skills/j-jbp/SKILL.md +32 -0
  49. package/skills/j-lgtm/SKILL.md +28 -0
  50. package/skills/{pi-plan → j-pi-plan}/SKILL.md +10 -3
  51. package/skills/{proceed → j-proceed}/SKILL.md +9 -2
  52. package/skills/{publish → j-publish}/SKILL.md +47 -40
  53. package/skills/{publish → j-publish}/adapters/droplet.md +1 -1
  54. package/skills/{publish → j-publish}/adapters/mobile-ios.md +3 -3
  55. package/skills/{publish → j-publish}/adapters/npm-ci.md +29 -7
  56. package/skills/{publish → j-publish}/adapters/npm.md +8 -8
  57. package/skills/{publish → j-publish}/assets/ci-contract.md +2 -2
  58. package/skills/{publish → j-publish}/schemas/publish.schema.json +1 -1
  59. package/skills/{publish → j-publish}/scripts/npm_ci_pipeline.sh +21 -1
  60. package/skills/{publish → j-publish}/scripts/npm_stage_inspect.sh +34 -1
  61. package/skills/{publish → j-publish}/scripts/npm_stage_pipeline.sh +9 -4
  62. package/skills/{publish → j-publish}/scripts/publish_deploy.sh +4 -4
  63. package/skills/{publish → j-publish}/scripts/validate_npm_stage_env.sh +1 -1
  64. package/skills/{publish → j-publish}/wizards/droplet.md +1 -1
  65. package/skills/{publish → j-publish}/wizards/mobile-ios.md +1 -1
  66. package/skills/{publish → j-publish}/wizards/npm-ci.md +1 -1
  67. package/skills/{publish → j-publish}/wizards/npm.md +1 -1
  68. package/skills/{reconcile → j-reconcile}/SKILL.md +12 -5
  69. package/skills/{reconcile → j-reconcile}/scripts/detect-unlinked-code.sh +2 -2
  70. package/skills/{reconcile → j-reconcile}/scripts/resolve-reconcile-scope.sh +3 -3
  71. package/skills/{reconcile-origin → j-reconcile-origin}/SKILL.md +13 -6
  72. package/skills/{redo → j-redo}/SKILL.md +9 -2
  73. package/skills/{skillify → j-skillify}/SKILL.md +10 -3
  74. package/skills/{spinoff → j-spinoff}/SKILL.md +9 -2
  75. package/skills/{status → j-status}/SKILL.md +9 -2
  76. package/skills/j-todo/SKILL.md +92 -0
  77. package/skills/{todo → j-todo}/assets/todo_handoff_template.md +1 -1
  78. package/skills/j-todo/scripts/add_trivial_task.sh +216 -0
  79. package/skills/j-todo/scripts/update_story_tasks.py +87 -0
  80. package/skills/{uncharted → j-uncharted}/SKILL.md +35 -28
  81. package/skills/{uncharted → j-uncharted}/assets/UNDERSTANDING_DOC_TEMPLATE.md +2 -2
  82. package/skills/{uncharted → j-uncharted}/scripts/detect-dependencies.sh +1 -1
  83. package/skills/{uncharted → j-uncharted}/scripts/detect-tests.sh +1 -1
  84. package/skills/{uncharted → j-uncharted}/scripts/directory-triage.sh +3 -3
  85. package/skills/{uncharted → j-uncharted}/scripts/elicitation-state.sh +3 -3
  86. package/skills/{uncharted → j-uncharted}/scripts/enumerate-target.sh +1 -1
  87. package/skills/{uncharted → j-uncharted}/scripts/import-source.sh +1 -1
  88. package/skills/{uncharted → j-uncharted}/scripts/inspect-provenance.sh +1 -1
  89. package/skills/{uncharted → j-uncharted}/scripts/resolve-segment-target.sh +5 -5
  90. package/skills/{uncharted → j-uncharted}/scripts/run-engine.sh +1 -1
  91. package/skills/{uncharted → j-uncharted}/scripts/validate-proposed-items.sh +2 -2
  92. package/skills/{uncharted → j-uncharted}/scripts/write-backfilled-epics.sh +1 -1
  93. package/skills/j-wtf/SKILL.md +27 -0
  94. package/skills/jenga/SKILL.md +1 -1
  95. package/skills/jenga/scripts/render-confirmation.sh +55 -18
  96. package/skills/jenga-permission-level/SKILL.md +1 -1
  97. package/templates/SCRUM_BOARD_SCHEMA.md +33 -2
  98. package/templates/agent-context.md.tpl +32 -9
  99. package/templates/copilot-instructions.md.tpl +66 -11
  100. package/skills/continue/SKILL.md +0 -29
  101. package/skills/error/SKILL.md +0 -29
  102. package/skills/examplify/SKILL.md +0 -42
  103. package/skills/init/assets/scope-thresholds_template.json +0 -7
  104. package/skills/jbp/SKILL.md +0 -25
  105. package/skills/lgtm/SKILL.md +0 -21
  106. package/skills/todo/SKILL.md +0 -48
  107. package/skills/wtf/SKILL.md +0 -20
  108. /package/skills/{close-story → j-close-story}/scripts/compute-scope-divergence.sh +0 -0
  109. /package/skills/{close-story → j-close-story}/scripts/extract-diff-stats.sh +0 -0
  110. /package/skills/{close-story → j-close-story}/scripts/update-task-frontmatter.sh +0 -0
  111. /package/skills/{commit → j-commit}/assets/user_instructions_template.md +0 -0
  112. /package/skills/{distribute → j-distribute}/CONFIG_SCHEMA.md +0 -0
  113. /package/skills/{distribute → j-distribute}/scripts/check-version.sh +0 -0
  114. /package/skills/{distribute → j-distribute}/scripts/commit-version-bump.sh +0 -0
  115. /package/skills/{do → j-do}/assets/intent-vs-diff-prompt.md +0 -0
  116. /package/skills/{do → j-do}/assets/sender_template.json +0 -0
  117. /package/skills/{doc → j-doc}/assets/path-objectives.yaml +0 -0
  118. /package/skills/{doc-sync → j-doc-sync}/assets/default_excludes.txt +0 -0
  119. /package/skills/{doc-sync → j-doc-sync}/assets/doc_targets.md +0 -0
  120. /package/skills/{evaluate → j-evaluate}/assets/evaluation_invokation_template.yml +0 -0
  121. /package/skills/{evaluate → j-evaluate}/assets/evaluation_rapport_template.md +0 -0
  122. /package/skills/{idea → j-idea}/assets/idea_template.md +0 -0
  123. /package/skills/{init → j-init}/assets/.gitignore_template +0 -0
  124. /package/skills/{init → j-init}/assets/PROJECT_SUMMARY_template.md +0 -0
  125. /package/skills/{init → j-init}/assets/directory_structure.txt +0 -0
  126. /package/skills/{init → j-init}/assets/strategy_stub_template.md +0 -0
  127. /package/skills/{init → j-init}/assets/test-config_template.json +0 -0
  128. /package/skills/{init → j-init}/assets/workflow_template.json +0 -0
  129. /package/skills/{init → j-init}/scripts/apply-project-visibility.sh +0 -0
  130. /package/skills/{init → j-init}/scripts/detect-existing-codebase.sh +0 -0
  131. /package/skills/{init → j-init}/scripts/init.sh +0 -0
  132. /package/skills/{pi-plan → j-pi-plan}/assets/epic.json +0 -0
  133. /package/skills/{pi-plan → j-pi-plan}/assets/story_template.md +0 -0
  134. /package/skills/{publish → j-publish}/assets/ExportOptions.plist.template +0 -0
  135. /package/skills/{publish → j-publish}/assets/ownership-matrix.md +0 -0
  136. /package/skills/{publish → j-publish}/assets/publish.example.json +0 -0
  137. /package/skills/{publish → j-publish}/assets/publish.example.npm-ci.json +0 -0
  138. /package/skills/{publish → j-publish}/assets/publish.example.npm.json +0 -0
  139. /package/skills/{publish → j-publish}/assets/secrets-guide.md +0 -0
  140. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-minimal.json +0 -0
  141. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-empty-secrets.json +0 -0
  142. /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-workflow-path.json +0 -0
  143. /package/skills/{publish → j-publish}/scripts/check_target_config.sh +0 -0
  144. /package/skills/{publish → j-publish}/scripts/droplet_pipeline.sh +0 -0
  145. /package/skills/{publish → j-publish}/scripts/finalize_changelog.sh +0 -0
  146. /package/skills/{publish → j-publish}/scripts/generate_release_notes.sh +0 -0
  147. /package/skills/{publish → j-publish}/scripts/ios_pipeline.sh +0 -0
  148. /package/skills/{publish → j-publish}/scripts/npm_pipeline.sh +0 -0
  149. /package/skills/{publish → j-publish}/scripts/publish_common.sh +0 -0
  150. /package/skills/{publish → j-publish}/scripts/reconcile_tags.sh +0 -0
  151. /package/skills/{publish → j-publish}/scripts/run_gates.sh +0 -0
  152. /package/skills/{publish → j-publish}/scripts/setup_wizard.sh +0 -0
  153. /package/skills/{publish → j-publish}/scripts/show_history.sh +0 -0
  154. /package/skills/{publish → j-publish}/scripts/suggest_semver_bump.sh +0 -0
  155. /package/skills/{publish → j-publish}/scripts/validate_config.sh +0 -0
  156. /package/skills/{publish → j-publish}/scripts/validate_droplet_env.sh +0 -0
  157. /package/skills/{publish → j-publish}/scripts/validate_ios_env.sh +0 -0
  158. /package/skills/{publish → j-publish}/scripts/validate_npm_ci_env.sh +0 -0
  159. /package/skills/{publish → j-publish}/scripts/validate_npm_env.sh +0 -0
  160. /package/skills/{publish → j-publish}/scripts/write_ledger_entry.sh +0 -0
  161. /package/skills/{reconcile → j-reconcile}/assets/report_format.md +0 -0
  162. /package/skills/{reconcile-origin → j-reconcile-origin}/scripts/reconcile-origin.sh +0 -0
  163. /package/skills/{skillify → j-skillify}/assets/init-new/SKILL.md +0 -0
  164. /package/skills/{skillify → j-skillify}/assets/init-new/assets/.gitignore_template +0 -0
  165. /package/skills/{skillify → j-skillify}/assets/init-new/assets/PROJECT_SUMMARY_template.md +0 -0
  166. /package/skills/{skillify → j-skillify}/assets/init-new/assets/directory_structure.txt +0 -0
  167. /package/skills/{skillify → j-skillify}/assets/init-new/assets/test-config_template.json +0 -0
  168. /package/skills/{skillify → j-skillify}/assets/init-new/assets/workflow_template.json +0 -0
  169. /package/skills/{skillify → j-skillify}/assets/init-new/scripts/init.sh +0 -0
  170. /package/skills/{skillify → j-skillify}/assets/init-old/SKILL.md +0 -0
  171. /package/skills/{status → j-status}/assets/output_format.md +0 -0
  172. /package/skills/{todo → j-todo}/assets/todo_template.md +0 -0
  173. /package/skills/{uncharted → j-uncharted}/assets/SEGMENT_PROPOSAL_TEMPLATE.md +0 -0
  174. /package/skills/{uncharted → j-uncharted}/scripts/apply-subsystem-cap.sh +0 -0
  175. /package/skills/{uncharted → j-uncharted}/scripts/discover-subsystems.sh +0 -0
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@jenga-ai/agent",
3
- "version": "1.3.0",
4
- "description": "Structured multi-agent development workflow for AI coding agents — scrum board, role-bounded scrum master / developer / tester agents, and 30+ slash-command skills. Works with Claude Code, GitHub Copilot, and Codex.",
3
+ "version": "3.0.0",
4
+ "description": "An agentic development workflow for Claude Code, Copilot, and Codex — with a persistent Epic/Story/Task board, an isolated git worktree per task, and a separate tester agent that runs your test suite before anything is marked done.",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "bin": {
10
- "jenga": "./bin/jenga.js"
10
+ "jenga": "bin/jenga.js"
11
11
  },
12
12
  "engines": {
13
13
  "node": ">=14.13.1"
@@ -54,22 +54,26 @@
54
54
  "url": "https://github.com/samwelmunga/jenga-npm/issues"
55
55
  },
56
56
  "keywords": [
57
- "ai-agents",
57
+ "ai",
58
+ "agent",
58
59
  "agentic",
59
- "agentic-workflow",
60
- "multi-agent",
61
- "agent-framework",
62
- "ai-development",
60
+ "workflow",
61
+ "framework",
62
+ "engineering",
63
+ "development",
64
+ "coding",
65
+ "context",
66
+ "software",
67
+ "kanban",
68
+ "board",
69
+ "management",
63
70
  "claude",
64
71
  "claude-code",
65
72
  "agents-md",
66
73
  "copilot",
67
- "github-copilot",
68
74
  "codex",
69
- "skills",
70
- "project-management",
71
- "developer-tools",
72
- "cli"
75
+ "jenga",
76
+ "jenga-ai"
73
77
  ],
74
78
  "author": {
75
79
  "name": "Samwel Munga",
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/apply-j-prefix.sh — mechanical j. prefix rename for skill invocation
3
+ #
4
+ # Implements the mechanism decided in docs/skill-authoring.md's "Invocation Convention"
5
+ # section (E50_S01_T01), with the separator corrected from ":" to "." in E50_S07_T01:
6
+ #
7
+ # 1. Frontmatter-only rename. For every skills/<name>/SKILL.md, rewrites the
8
+ # frontmatter `name:` field from `<name>` to `j.<name>`. Directory names are
9
+ # left untouched — neither ":" nor "." is worth putting in a path here, and
10
+ # Claude Code's native skill resolution is a literal-string match against the
11
+ # discovery-path directory name, independent of frontmatter content (see the
12
+ # docs section for the full investigation and citation).
13
+ # 2. Bare-form prose rewrite. For every agents/*.md file, rewrites bare
14
+ # "/<name>" invocation mentions in prose to "j.<name>", for every skill name
15
+ # discovered in step 1 — longest-name-first, boundary-safe (skill names
16
+ # contain hyphens, so a naive `\b` regex would misfire — e.g. it would
17
+ # falsely match "/doc" inside "/doc-sync"; this only rewrites a match when
18
+ # the character immediately before/after it is not itself an identifier
19
+ # character: letter, digit, "-", or ":").
20
+ #
21
+ # Separator history (E50_S07): this script originally emitted "j:<name>". GitHub
22
+ # Copilot CLI validates the frontmatter `name:` VALUE and rejects anything outside
23
+ # [A-Za-z0-9], hyphen, underscore, dot, and space — so the colon made every skill
24
+ # fail to load there. The separator is now "." which is on that allow-list. The
25
+ # namespace intent of E50 is unchanged; only the separator character moved.
26
+ #
27
+ # This script does NOT remove or disable the old bare "/<name>" form. The
28
+ # migration decision recorded alongside the mechanism above is "alias" — both
29
+ # forms keep resolving. This script only ever writes inside skills/ and
30
+ # agents/; it never touches .claude/ or .agents/ (generated mirrors — see
31
+ # CLAUDE.md's Source of Truth rule) and makes no other filesystem change.
32
+ #
33
+ # Usage:
34
+ # scripts/apply-j-prefix.sh [--dry-run] [--skills-only | --agents-only]
35
+ #
36
+ # --dry-run Print the full change list without writing anything.
37
+ # Exit code is 0 whether or not changes were found (unless
38
+ # --skills-only/--agents-only surface a hard error, e.g. a
39
+ # frontmatter `name:` that doesn't match its directory).
40
+ # --skills-only Only process skills/*/SKILL.md frontmatter (step 1).
41
+ # --agents-only Only process agents/*.md prose (step 2).
42
+ #
43
+ # Idempotent: already-migrated skills (name already starts with "j.") are
44
+ # silently skipped, not re-applied or reported as an error. Skills still carrying
45
+ # the legacy "j:<name>" form are REPAIRED to "j.<name>" rather than skipped —
46
+ # skipping them would silently leave a Copilot-breaking name in place.
47
+
48
+ set -euo pipefail
49
+
50
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
51
+ # shellcheck source=lib/resolve-project-dir.sh
52
+ source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
53
+
54
+ PROJECT_DIR="$JENGA_PROJECT_DIR"
55
+ SKILLS_DIR="$PROJECT_DIR/skills"
56
+ AGENTS_DIR="$PROJECT_DIR/agents"
57
+
58
+ DRY_RUN=0
59
+ DO_SKILLS=1
60
+ DO_AGENTS=1
61
+
62
+ for arg in "$@"; do
63
+ case "$arg" in
64
+ --dry-run) DRY_RUN=1 ;;
65
+ --skills-only) DO_AGENTS=0 ;;
66
+ --agents-only) DO_SKILLS=0 ;;
67
+ -h|--help)
68
+ echo "Usage: $(basename "$0") [--dry-run] [--skills-only | --agents-only]"
69
+ exit 0
70
+ ;;
71
+ *)
72
+ echo "❌ Unknown argument: $arg" >&2
73
+ echo "Usage: $(basename "$0") [--dry-run] [--skills-only | --agents-only]" >&2
74
+ exit 1
75
+ ;;
76
+ esac
77
+ done
78
+
79
+ export DRY_RUN DO_SKILLS DO_AGENTS SKILLS_DIR AGENTS_DIR
80
+
81
+ python3 - <<'PY'
82
+ import os
83
+ import re
84
+ import sys
85
+
86
+ skills_dir = os.environ["SKILLS_DIR"]
87
+ agents_dir = os.environ["AGENTS_DIR"]
88
+ dry_run = os.environ["DRY_RUN"] == "1"
89
+ do_skills = os.environ["DO_SKILLS"] == "1"
90
+ do_agents = os.environ["DO_AGENTS"] == "1"
91
+
92
+ changes = []
93
+ errors = []
94
+
95
+ # ---- Step 1: discover skills + (optionally) rewrite frontmatter name: field ----
96
+ skill_names = [] # bare directory names, in sorted order
97
+ name_re = re.compile(r'^name:\s*(.+?)\s*$')
98
+
99
+ if os.path.isdir(skills_dir):
100
+ for entry in sorted(os.listdir(skills_dir)):
101
+ skill_path = os.path.join(skills_dir, entry, "SKILL.md")
102
+ if not os.path.isfile(skill_path):
103
+ continue # not a skill dir (e.g. skills/index/ has no SKILL.md)
104
+
105
+ with open(skill_path, "r", encoding="utf-8") as f:
106
+ lines = f.readlines()
107
+
108
+ if not lines or lines[0].strip() != "---":
109
+ errors.append(f"{skill_path}: no frontmatter block found")
110
+ continue
111
+
112
+ close_idx = None
113
+ for i in range(1, len(lines)):
114
+ if lines[i].strip() == "---":
115
+ close_idx = i
116
+ break
117
+ if close_idx is None:
118
+ errors.append(f"{skill_path}: unterminated frontmatter block")
119
+ continue
120
+
121
+ name_idx = None
122
+ current_name = None
123
+ for i in range(1, close_idx):
124
+ m = name_re.match(lines[i])
125
+ if m:
126
+ name_idx = i
127
+ current_name = m.group(1).strip('"\'')
128
+ break
129
+
130
+ if name_idx is None:
131
+ errors.append(f"{skill_path}: no 'name:' field in frontmatter")
132
+ continue
133
+
134
+ skill_names.append(entry)
135
+
136
+ if current_name.startswith("j."):
137
+ continue # already migrated — not an error, nothing to do
138
+
139
+ # Legacy "j:<name>" form (emitted by this script before E50_S07_T01).
140
+ # The colon is rejected by GitHub Copilot CLI's skill-name validator, so
141
+ # this is repaired to "j.<name>" rather than skipped as "already migrated".
142
+ base_name = current_name[2:] if current_name.startswith("j:") else current_name
143
+
144
+ if base_name != entry:
145
+ errors.append(
146
+ f"{skill_path}: frontmatter name '{current_name}' does not match "
147
+ f"directory name '{entry}' — skipped for safety"
148
+ )
149
+ continue
150
+
151
+ if not do_skills:
152
+ continue
153
+
154
+ new_line = f"name: j.{entry}\n"
155
+ changes.append({
156
+ "type": "frontmatter",
157
+ "file": skill_path,
158
+ "before": lines[name_idx].rstrip("\n"),
159
+ "after": new_line.rstrip("\n"),
160
+ })
161
+
162
+ if not dry_run:
163
+ lines[name_idx] = new_line
164
+ with open(skill_path, "w", encoding="utf-8") as f:
165
+ f.writelines(lines)
166
+ elif do_skills:
167
+ errors.append(f"skills directory not found: {skills_dir}")
168
+
169
+ # ---- Step 2: rewrite bare "/<name>" prose mentions in agents/*.md ----
170
+ if do_agents and os.path.isdir(agents_dir):
171
+ # Longest name first so "/jenga-permission-level" is matched (and
172
+ # consumed) before the shorter "/jenga" gets a chance to.
173
+ ordered_names = sorted(skill_names, key=len, reverse=True)
174
+ boundary_chars = set(
175
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-:"
176
+ )
177
+
178
+ for entry in sorted(os.listdir(agents_dir)):
179
+ if not entry.endswith(".md"):
180
+ continue
181
+ agent_path = os.path.join(agents_dir, entry)
182
+ if not os.path.isfile(agent_path):
183
+ continue
184
+
185
+ with open(agent_path, "r", encoding="utf-8") as f:
186
+ text = f.read()
187
+
188
+ file_changes = 0
189
+
190
+ for name in ordered_names:
191
+ pattern = re.compile(r"/" + re.escape(name))
192
+ matches = []
193
+ for m in pattern.finditer(text):
194
+ start, end = m.start(), m.end()
195
+ if start > 0 and text[start - 1] in boundary_chars:
196
+ continue # part of a longer token, e.g. "x/status"
197
+ if end < len(text) and text[end] in boundary_chars:
198
+ continue # e.g. "/doc" inside "/doc-sync"
199
+ matches.append((start, end))
200
+ for start, end in reversed(matches):
201
+ text = text[:start] + f"j.{name}" + text[end:]
202
+ file_changes += 1
203
+
204
+ if file_changes:
205
+ changes.append({
206
+ "type": "agent-prose",
207
+ "file": agent_path,
208
+ "count": file_changes,
209
+ })
210
+ if not dry_run:
211
+ with open(agent_path, "w", encoding="utf-8") as f:
212
+ f.write(text)
213
+ elif do_agents and not os.path.isdir(agents_dir):
214
+ errors.append(f"agents directory not found: {agents_dir}")
215
+
216
+ # ---- report ----
217
+ mode = "DRY RUN — no files written" if dry_run else "APPLIED"
218
+ print(f"apply-j-prefix.sh — {mode}")
219
+ print(f"Skills discovered: {len(skill_names)}")
220
+ print()
221
+
222
+ if not changes:
223
+ print("No changes.")
224
+ else:
225
+ for c in changes:
226
+ if c["type"] == "frontmatter":
227
+ print(f"[frontmatter] {c['file']}")
228
+ print(f" - {c['before']}")
229
+ print(f" + {c['after']}")
230
+ else:
231
+ print(f"[agent-prose] {c['file']} — {c['count']} occurrence(s) rewritten")
232
+ print()
233
+ print(f"Total: {len(changes)} change group(s)")
234
+
235
+ if errors:
236
+ print()
237
+ print("Errors / skipped:")
238
+ for e in errors:
239
+ print(f" ⚠ {e}")
240
+ sys.exit(1)
241
+
242
+ sys.exit(0)
243
+ PY
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # scripts/consume-context-digest.sh
4
+ #
5
+ # Read-and-delete helper for the resolved_context digest handoff convention
6
+ # (E49_S01). The receiving subagent (the one whose sender object carries a
7
+ # resolved_context / resolved_context_path pointing at a file under
8
+ # project/queue/context/) calls this once, as part of reading its own sender
9
+ # object, to fetch the digest content and immediately mark the file consumed
10
+ # — mirroring project/queue/handoffs/'s single-use lifecycle rather than
11
+ # leaving cleanup to a convention every receiver has to remember to follow.
12
+ #
13
+ # Atomic claim: before reading, the target file is renamed to a
14
+ # `.claimed.$$` sibling in the same directory (same TOCTOU-safe pattern
15
+ # hooks/on_session_end.sh section 4 already uses when routing
16
+ # project/queue/handoffs/ files). `mv` between two paths on the same
17
+ # filesystem is a rename(2) syscall — the kernel guarantees exactly one
18
+ # concurrent claimer wins. If the rename fails, another process already
19
+ # consumed (or is consuming) this file; this invocation exits 3 without
20
+ # printing anything, rather than risking a double-read of a file that's
21
+ # about to disappear out from under it.
22
+ #
23
+ # This script does NOT sweep for abandoned digests nobody ever consumes —
24
+ # that backstop is scripts/sweep-stale-context-digests.sh, invoked from
25
+ # hooks/on_session_end.sh.
26
+ #
27
+ # Usage:
28
+ # consume-context-digest.sh <path-to-digest-file>
29
+ # consume-context-digest.sh --raw <path-to-digest-file>
30
+ #
31
+ # Options:
32
+ # --raw print only the digest field's text (not the full JSON envelope)
33
+ # -h, --help
34
+ #
35
+ # Output (stdout):
36
+ # the digest file's full JSON envelope (default), or just its `digest`
37
+ # field's text with --raw
38
+ #
39
+ # Exit codes:
40
+ # 0 digest read and file deleted
41
+ # 1 usage error
42
+ # 2 target file does not exist (already consumed, or never written)
43
+ # 3 target file was claimed by a concurrent consumer first
44
+ # 4 target file is not valid JSON / missing expected fields
45
+ # ---------------------------------------------------------------------------
46
+
47
+ set -euo pipefail
48
+
49
+ SELF="$(basename "$0")"
50
+
51
+ die() {
52
+ local code="$1"; shift
53
+ printf '%s: error: %s\n' "$SELF" "$*" >&2
54
+ exit "$code"
55
+ }
56
+
57
+ usage() {
58
+ sed -n '/^# Usage:/,/^# Exit codes:/p' "$0" | sed 's/^# \{0,1\}//' >&2
59
+ exit 1
60
+ }
61
+
62
+ command -v jq >/dev/null 2>&1 || die 1 "jq is required but not found on PATH"
63
+
64
+ RAW=0
65
+ TARGET=""
66
+
67
+ while [ "$#" -gt 0 ]; do
68
+ case "$1" in
69
+ --raw)
70
+ RAW=1; shift ;;
71
+ -h|--help)
72
+ usage ;;
73
+ -*)
74
+ die 1 "unknown option: $1" ;;
75
+ *)
76
+ [ -z "$TARGET" ] || die 1 "unexpected extra argument: $1"
77
+ TARGET="$1"; shift ;;
78
+ esac
79
+ done
80
+
81
+ [ -n "$TARGET" ] || usage
82
+ [ -f "$TARGET" ] || die 2 "digest file not found: $TARGET (already consumed, or never written)"
83
+
84
+ CLAIMED_FILE="${TARGET}.claimed.$$"
85
+ if ! mv "$TARGET" "$CLAIMED_FILE" 2>/dev/null; then
86
+ die 3 "digest file '$TARGET' was claimed by a concurrent consumer — skipping"
87
+ fi
88
+
89
+ # Always attempt to remove the claimed temp file, whether jq succeeds or not,
90
+ # so a malformed digest can't leave a permanent orphan behind.
91
+ cleanup() {
92
+ rm -f "$CLAIMED_FILE" 2>/dev/null || true
93
+ }
94
+ trap cleanup EXIT
95
+
96
+ if [ "$RAW" -eq 1 ]; then
97
+ DIGEST_TEXT=$(jq -r '.digest // empty' "$CLAIMED_FILE" 2>/dev/null) || die 4 "'$TARGET' is not valid digest JSON"
98
+ [ -n "$DIGEST_TEXT" ] || die 4 "'$TARGET' has no 'digest' field"
99
+ printf '%s\n' "$DIGEST_TEXT"
100
+ else
101
+ jq -e '.' "$CLAIMED_FILE" >/dev/null 2>&1 || die 4 "'$TARGET' is not valid JSON"
102
+ cat "$CLAIMED_FILE"
103
+ fi