@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.
- package/README.md +93 -256
- package/agents/developer.md +9 -8
- package/agents/scrum-master.md +57 -23
- package/agents/tester.md +51 -5
- package/hooks/on_session_end.sh +13 -1
- package/lib/generate-agent-context.js +18 -1
- package/lib/generate-copilot-instructions.js +18 -1
- package/lib/generate-skill-allow-list.js +197 -0
- package/lib/skill-allow-list.json +42 -0
- package/package.json +17 -13
- package/scripts/apply-j-prefix.sh +243 -0
- package/scripts/consume-context-digest.sh +103 -0
- package/scripts/generate-j-alias.sh +333 -0
- package/scripts/postinstall.js +25 -0
- package/scripts/sweep-stale-context-digests.sh +132 -0
- package/scripts/validate-board.sh +5 -0
- package/scripts/write-context-digest.sh +230 -0
- package/skills/{brainstorm → j-brainstorm}/SKILL.md +9 -2
- package/skills/{btw → j-btw}/SKILL.md +9 -2
- package/skills/{clearify → j-clearify}/SKILL.md +9 -2
- package/skills/{close-story → j-close-story}/SKILL.md +92 -13
- package/skills/j-close-story/scripts/check-privatized.sh +345 -0
- package/skills/{close-story → j-close-story}/scripts/check-story-closeable.sh +1 -1
- package/skills/{close-story → j-close-story}/scripts/extract-task-diff-stats.sh +1 -1
- package/skills/{commit → j-commit}/SKILL.md +9 -2
- package/skills/j-continue/SKILL.md +36 -0
- package/skills/{deep-dive → j-deep-dive}/SKILL.md +9 -8
- package/skills/{dev-done → j-dev-done}/SKILL.md +11 -4
- package/skills/{dev-done → j-dev-done}/scripts/classify-commit-outcome.sh +4 -4
- package/skills/{distribute → j-distribute}/SKILL.md +17 -10
- package/skills/{distribute → j-distribute}/scripts/distribute-changes.sh +1 -1
- package/skills/{do → j-do}/SKILL.md +111 -14
- package/skills/j-doc/README.md +155 -0
- package/skills/{doc → j-doc}/SKILL.md +55 -18
- package/skills/j-doc/authoring-notes.md +72 -0
- package/skills/j-doc/scripts/resolve_last_update.py +149 -0
- package/skills/{doc-sync → j-doc-sync}/SKILL.md +9 -2
- package/skills/{dooo → j-dooo}/SKILL.md +9 -2
- package/skills/j-error/SKILL.md +36 -0
- package/skills/{evaluate → j-evaluate}/SKILL.md +9 -2
- package/skills/j-examplify/SKILL.md +49 -0
- package/skills/{help → j-help}/SKILL.md +9 -2
- package/skills/{idea → j-idea}/SKILL.md +10 -3
- package/skills/{idea → j-idea}/assets/idea_handoff_template.md +1 -1
- package/skills/{improve → j-improve}/SKILL.md +9 -2
- package/skills/{init → j-init}/SKILL.md +21 -8
- package/skills/j-init/assets/scope-thresholds_template.json +7 -0
- package/skills/j-jbp/SKILL.md +32 -0
- package/skills/j-lgtm/SKILL.md +28 -0
- package/skills/{pi-plan → j-pi-plan}/SKILL.md +10 -3
- package/skills/{proceed → j-proceed}/SKILL.md +9 -2
- package/skills/{publish → j-publish}/SKILL.md +47 -40
- package/skills/{publish → j-publish}/adapters/droplet.md +1 -1
- package/skills/{publish → j-publish}/adapters/mobile-ios.md +3 -3
- package/skills/{publish → j-publish}/adapters/npm-ci.md +29 -7
- package/skills/{publish → j-publish}/adapters/npm.md +8 -8
- package/skills/{publish → j-publish}/assets/ci-contract.md +2 -2
- package/skills/{publish → j-publish}/schemas/publish.schema.json +1 -1
- package/skills/{publish → j-publish}/scripts/npm_ci_pipeline.sh +21 -1
- package/skills/{publish → j-publish}/scripts/npm_stage_inspect.sh +34 -1
- package/skills/{publish → j-publish}/scripts/npm_stage_pipeline.sh +9 -4
- package/skills/{publish → j-publish}/scripts/publish_deploy.sh +4 -4
- package/skills/{publish → j-publish}/scripts/validate_npm_stage_env.sh +1 -1
- package/skills/{publish → j-publish}/wizards/droplet.md +1 -1
- package/skills/{publish → j-publish}/wizards/mobile-ios.md +1 -1
- package/skills/{publish → j-publish}/wizards/npm-ci.md +1 -1
- package/skills/{publish → j-publish}/wizards/npm.md +1 -1
- package/skills/{reconcile → j-reconcile}/SKILL.md +12 -5
- package/skills/{reconcile → j-reconcile}/scripts/detect-unlinked-code.sh +2 -2
- package/skills/{reconcile → j-reconcile}/scripts/resolve-reconcile-scope.sh +3 -3
- package/skills/{reconcile-origin → j-reconcile-origin}/SKILL.md +13 -6
- package/skills/{redo → j-redo}/SKILL.md +9 -2
- package/skills/{skillify → j-skillify}/SKILL.md +10 -3
- package/skills/{spinoff → j-spinoff}/SKILL.md +9 -2
- package/skills/{status → j-status}/SKILL.md +9 -2
- package/skills/j-todo/SKILL.md +92 -0
- package/skills/{todo → j-todo}/assets/todo_handoff_template.md +1 -1
- package/skills/j-todo/scripts/add_trivial_task.sh +216 -0
- package/skills/j-todo/scripts/update_story_tasks.py +87 -0
- package/skills/{uncharted → j-uncharted}/SKILL.md +35 -28
- package/skills/{uncharted → j-uncharted}/assets/UNDERSTANDING_DOC_TEMPLATE.md +2 -2
- package/skills/{uncharted → j-uncharted}/scripts/detect-dependencies.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/detect-tests.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/directory-triage.sh +3 -3
- package/skills/{uncharted → j-uncharted}/scripts/elicitation-state.sh +3 -3
- package/skills/{uncharted → j-uncharted}/scripts/enumerate-target.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/import-source.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/inspect-provenance.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/resolve-segment-target.sh +5 -5
- package/skills/{uncharted → j-uncharted}/scripts/run-engine.sh +1 -1
- package/skills/{uncharted → j-uncharted}/scripts/validate-proposed-items.sh +2 -2
- package/skills/{uncharted → j-uncharted}/scripts/write-backfilled-epics.sh +1 -1
- package/skills/j-wtf/SKILL.md +27 -0
- package/skills/jenga/SKILL.md +1 -1
- package/skills/jenga/scripts/render-confirmation.sh +55 -18
- package/skills/jenga-permission-level/SKILL.md +1 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +33 -2
- package/templates/agent-context.md.tpl +32 -9
- package/templates/copilot-instructions.md.tpl +66 -11
- package/skills/continue/SKILL.md +0 -29
- package/skills/error/SKILL.md +0 -29
- package/skills/examplify/SKILL.md +0 -42
- package/skills/init/assets/scope-thresholds_template.json +0 -7
- package/skills/jbp/SKILL.md +0 -25
- package/skills/lgtm/SKILL.md +0 -21
- package/skills/todo/SKILL.md +0 -48
- package/skills/wtf/SKILL.md +0 -20
- /package/skills/{close-story → j-close-story}/scripts/compute-scope-divergence.sh +0 -0
- /package/skills/{close-story → j-close-story}/scripts/extract-diff-stats.sh +0 -0
- /package/skills/{close-story → j-close-story}/scripts/update-task-frontmatter.sh +0 -0
- /package/skills/{commit → j-commit}/assets/user_instructions_template.md +0 -0
- /package/skills/{distribute → j-distribute}/CONFIG_SCHEMA.md +0 -0
- /package/skills/{distribute → j-distribute}/scripts/check-version.sh +0 -0
- /package/skills/{distribute → j-distribute}/scripts/commit-version-bump.sh +0 -0
- /package/skills/{do → j-do}/assets/intent-vs-diff-prompt.md +0 -0
- /package/skills/{do → j-do}/assets/sender_template.json +0 -0
- /package/skills/{doc → j-doc}/assets/path-objectives.yaml +0 -0
- /package/skills/{doc-sync → j-doc-sync}/assets/default_excludes.txt +0 -0
- /package/skills/{doc-sync → j-doc-sync}/assets/doc_targets.md +0 -0
- /package/skills/{evaluate → j-evaluate}/assets/evaluation_invokation_template.yml +0 -0
- /package/skills/{evaluate → j-evaluate}/assets/evaluation_rapport_template.md +0 -0
- /package/skills/{idea → j-idea}/assets/idea_template.md +0 -0
- /package/skills/{init → j-init}/assets/.gitignore_template +0 -0
- /package/skills/{init → j-init}/assets/PROJECT_SUMMARY_template.md +0 -0
- /package/skills/{init → j-init}/assets/directory_structure.txt +0 -0
- /package/skills/{init → j-init}/assets/strategy_stub_template.md +0 -0
- /package/skills/{init → j-init}/assets/test-config_template.json +0 -0
- /package/skills/{init → j-init}/assets/workflow_template.json +0 -0
- /package/skills/{init → j-init}/scripts/apply-project-visibility.sh +0 -0
- /package/skills/{init → j-init}/scripts/detect-existing-codebase.sh +0 -0
- /package/skills/{init → j-init}/scripts/init.sh +0 -0
- /package/skills/{pi-plan → j-pi-plan}/assets/epic.json +0 -0
- /package/skills/{pi-plan → j-pi-plan}/assets/story_template.md +0 -0
- /package/skills/{publish → j-publish}/assets/ExportOptions.plist.template +0 -0
- /package/skills/{publish → j-publish}/assets/ownership-matrix.md +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.json +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.npm-ci.json +0 -0
- /package/skills/{publish → j-publish}/assets/publish.example.npm.json +0 -0
- /package/skills/{publish → j-publish}/assets/secrets-guide.md +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-minimal.json +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-empty-secrets.json +0 -0
- /package/skills/{publish → j-publish}/schemas/fixtures/npm-ci-with-workflow-path.json +0 -0
- /package/skills/{publish → j-publish}/scripts/check_target_config.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/droplet_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/finalize_changelog.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/generate_release_notes.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/ios_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/npm_pipeline.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/publish_common.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/reconcile_tags.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/run_gates.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/setup_wizard.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/show_history.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/suggest_semver_bump.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_config.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_droplet_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_ios_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_npm_ci_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/validate_npm_env.sh +0 -0
- /package/skills/{publish → j-publish}/scripts/write_ledger_entry.sh +0 -0
- /package/skills/{reconcile → j-reconcile}/assets/report_format.md +0 -0
- /package/skills/{reconcile-origin → j-reconcile-origin}/scripts/reconcile-origin.sh +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/SKILL.md +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/.gitignore_template +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/PROJECT_SUMMARY_template.md +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/directory_structure.txt +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/test-config_template.json +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/assets/workflow_template.json +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-new/scripts/init.sh +0 -0
- /package/skills/{skillify → j-skillify}/assets/init-old/SKILL.md +0 -0
- /package/skills/{status → j-status}/assets/output_format.md +0 -0
- /package/skills/{todo → j-todo}/assets/todo_template.md +0 -0
- /package/skills/{uncharted → j-uncharted}/assets/SEGMENT_PROPOSAL_TEMPLATE.md +0 -0
- /package/skills/{uncharted → j-uncharted}/scripts/apply-subsystem-cap.sh +0 -0
- /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": "
|
|
4
|
-
"description": "
|
|
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": "
|
|
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
|
|
57
|
+
"ai",
|
|
58
|
+
"agent",
|
|
58
59
|
"agentic",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
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
|
-
"
|
|
70
|
-
"
|
|
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
|