@jenga-ai/agent 1.2.4 → 2.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 (100) hide show
  1. package/README.md +97 -91
  2. package/agents/developer.md +26 -7
  3. package/agents/scrum-master.md +57 -22
  4. package/agents/tester.md +68 -4
  5. package/hooks/on_session_end.sh +40 -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 +191 -0
  9. package/lib/skill-allow-list.json +43 -0
  10. package/package.json +35 -20
  11. package/scripts/apply-j-prefix.sh +230 -0
  12. package/scripts/consume-context-digest.sh +103 -0
  13. package/scripts/postinstall.js +25 -0
  14. package/scripts/sweep-stale-context-digests.sh +132 -0
  15. package/scripts/validate-board.sh +5 -0
  16. package/scripts/write-context-digest.sh +230 -0
  17. package/skills/brainstorm/SKILL.md +1 -1
  18. package/skills/btw/SKILL.md +1 -1
  19. package/skills/clearify/SKILL.md +1 -1
  20. package/skills/close-story/SKILL.md +78 -6
  21. package/skills/close-story/scripts/check-privatized.sh +345 -0
  22. package/skills/commit/SKILL.md +12 -2
  23. package/skills/continue/SKILL.md +1 -1
  24. package/skills/deep-dive/SKILL.md +1 -1
  25. package/skills/dev-done/SKILL.md +46 -0
  26. package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
  27. package/skills/distribute/SKILL.md +1 -1
  28. package/skills/do/SKILL.md +100 -10
  29. package/skills/doc/README.md +155 -0
  30. package/skills/doc/SKILL.md +43 -13
  31. package/skills/doc/authoring-notes.md +72 -0
  32. package/skills/doc/scripts/resolve_last_update.py +149 -0
  33. package/skills/doc-sync/SKILL.md +1 -1
  34. package/skills/dooo/SKILL.md +1 -1
  35. package/skills/error/SKILL.md +1 -1
  36. package/skills/evaluate/SKILL.md +1 -1
  37. package/skills/examplify/SKILL.md +1 -1
  38. package/skills/help/SKILL.md +1 -1
  39. package/skills/idea/SKILL.md +1 -1
  40. package/skills/improve/SKILL.md +1 -1
  41. package/skills/init/SKILL.md +8 -7
  42. package/skills/init/assets/scope-thresholds_template.json +7 -0
  43. package/skills/init/scripts/init.sh +6 -0
  44. package/skills/j-init/SKILL.md +168 -0
  45. package/skills/j-init/assets/.gitignore_template +15 -0
  46. package/skills/j-init/assets/PROJECT_SUMMARY_template.md +13 -0
  47. package/skills/j-init/assets/directory_structure.txt +14 -0
  48. package/skills/j-init/assets/scope-thresholds_template.json +7 -0
  49. package/skills/j-init/assets/strategy_stub_template.md +38 -0
  50. package/skills/j-init/assets/test-config_template.json +4 -0
  51. package/skills/j-init/assets/workflow_template.json +30 -0
  52. package/skills/j-init/scripts/apply-project-visibility.sh +176 -0
  53. package/skills/j-init/scripts/detect-existing-codebase.sh +166 -0
  54. package/skills/j-init/scripts/init.sh +116 -0
  55. package/skills/jbp/SKILL.md +1 -1
  56. package/skills/jenga/SKILL.md +1 -1
  57. package/skills/jenga/scripts/render-confirmation.sh +55 -18
  58. package/skills/jenga-permission-level/SKILL.md +1 -1
  59. package/skills/lgtm/SKILL.md +1 -1
  60. package/skills/pi-plan/SKILL.md +1 -1
  61. package/skills/proceed/SKILL.md +1 -1
  62. package/skills/publish/SKILL.md +67 -1
  63. package/skills/publish/adapters/npm-ci.md +60 -4
  64. package/skills/publish/adapters/npm.md +18 -0
  65. package/skills/publish/assets/ci-contract.md +27 -0
  66. package/skills/publish/assets/publish.example.json +27 -0
  67. package/skills/publish/schemas/publish.schema.json +20 -0
  68. package/skills/publish/scripts/npm_ci_pipeline.sh +50 -1
  69. package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
  70. package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
  71. package/skills/publish/scripts/publish_common.sh +16 -0
  72. package/skills/publish/scripts/show_history.sh +12 -5
  73. package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
  74. package/skills/publish/scripts/write_ledger_entry.sh +92 -2
  75. package/skills/reconcile/SKILL.md +122 -12
  76. package/skills/reconcile/assets/report_format.md +17 -0
  77. package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
  78. package/skills/reconcile-origin/SKILL.md +1 -1
  79. package/skills/redo/SKILL.md +1 -1
  80. package/skills/skillify/SKILL.md +1 -1
  81. package/skills/spinoff/SKILL.md +1 -1
  82. package/skills/status/SKILL.md +1 -1
  83. package/skills/todo/SKILL.md +40 -3
  84. package/skills/todo/scripts/add_trivial_task.sh +216 -0
  85. package/skills/todo/scripts/update_story_tasks.py +87 -0
  86. package/skills/uncharted/SKILL.md +201 -22
  87. package/skills/uncharted/scripts/directory-triage.sh +342 -0
  88. package/skills/uncharted/scripts/elicitation-state.sh +457 -0
  89. package/skills/wtf/SKILL.md +1 -1
  90. package/templates/SCRUM_BOARD_SCHEMA.md +90 -2
  91. package/templates/agent-context.md.tpl +47 -12
  92. package/templates/copilot-instructions.md.tpl +36 -9
  93. package/mcp/router/README.md +0 -19
  94. package/mcp/router/embedder.js +0 -23
  95. package/mcp/router/index.js +0 -204
  96. package/mcp/router/matcher.js +0 -87
  97. package/mcp/router/package-lock.json +0 -1048
  98. package/mcp/router/package.json +0 -11
  99. package/mcp/router/skill-index.js +0 -104
  100. package/skills/route/SKILL.md +0 -180
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/uncharted/scripts/directory-triage.sh
4
+ #
5
+ # Deterministic front half of the directory-triage step (E20_S08_T03) that
6
+ # sits between discovery and any developer/tester Investigative Mode
7
+ # dispatch, for both `/uncharted onboard`'s conversational default and
8
+ # `/uncharted segment --mode investigate`.
9
+ #
10
+ # The problem: an investigative pass that walks every directory in a
11
+ # candidate set wastes turns (and, worse, a user's attention at the
12
+ # confirmation gate) on vendor/generated noise nobody wants a coarse graph
13
+ # node for. This script answers the MECHANICAL half of that question —
14
+ # "is this directory already excluded by .gitignore, or does it match a
15
+ # well-known vendor/generated pattern?" — so the agent only has to spend
16
+ # judgement on what's left: the content-based "generalize" cases a fixed
17
+ # pattern list can never catch (e.g. "this directory is SOAP request
18
+ # mock-ups for a test suite").
19
+ #
20
+ # This script performs NO judgement of its own and writes nothing. It
21
+ # classifies; the agent decides what to do with `remaining`.
22
+ #
23
+ # ---------------------------------------------------------------------------
24
+ # INPUT
25
+ # ---------------------------------------------------------------------------
26
+ # A root (the repository, or the target `onboard`/`segment` is investigating)
27
+ # and a set of candidate directory paths to triage, relative to that root.
28
+ # Candidates come from positional arguments, or newline-delimited from stdin
29
+ # when none are given — composable with discover-subsystems.sh the same way
30
+ # apply-subsystem-cap.sh is:
31
+ #
32
+ # discover-subsystems.sh <root> | jq -r '.candidates[].path' \
33
+ # | directory-triage.sh <root>
34
+ #
35
+ # A candidate that does not exist under <root>, or is not a directory, is
36
+ # reported in `notices` and otherwise skipped — it is an input problem, not a
37
+ # triage verdict.
38
+ #
39
+ # ---------------------------------------------------------------------------
40
+ # THE DETERMINISTIC IGNORE-LIST
41
+ # ---------------------------------------------------------------------------
42
+ # Path-COMPONENT match against a fixed, known-noise pattern list — never a
43
+ # substring match, so a real subsystem directory named e.g. "targets/" is not
44
+ # caught by the "target" pattern. The list is intentionally short and named
45
+ # in the task's own acceptance criteria; it is expected to need occasional
46
+ # extension as new ecosystems are triaged, and is kept as a single array
47
+ # below for exactly that reason — this is a revisitable list, not a closed
48
+ # one:
49
+ #
50
+ # node_modules vendor dist build .venv
51
+ # venv __pycache__ .next coverage target
52
+ # .git .tox .mypy_cache .pytest_cache .cache
53
+ # *.egg-info (suffix match on the last path component)
54
+ #
55
+ # A candidate already excluded by .gitignore (via `git check-ignore`) is
56
+ # reported separately (`reason: "gitignore"`) even if it ALSO matches a
57
+ # pattern — gitignore is checked first and wins, since "why is this
58
+ # ignored" should point at the actual mechanism in effect.
59
+ #
60
+ # ---------------------------------------------------------------------------
61
+ # OPTIONS
62
+ # ---------------------------------------------------------------------------
63
+ # --root DIR Root the candidates are resolved against. Required as
64
+ # the first positional argument OR via this flag.
65
+ # --json-out FILE Also write the JSON report to a file. stdout gets it
66
+ # regardless.
67
+ # -h, --help Show this help and exit 0.
68
+ #
69
+ # ---------------------------------------------------------------------------
70
+ # OUTPUT (stdout, JSON)
71
+ # ---------------------------------------------------------------------------
72
+ # {
73
+ # "script": "directory-triage.sh",
74
+ # "version": 1,
75
+ # "root": "<as given>",
76
+ # "root_absolute": "<canonicalised>",
77
+ # "candidate_count": <int>,
78
+ # "ignored_count": <int>,
79
+ # "remaining_count": <int>,
80
+ # "ignored": [ { "path", "absolute_path", "reason" }, ... ],
81
+ # "remaining": [ { "path", "absolute_path" }, ... ],
82
+ # "notices": [ "<non-fatal diagnostic>", ... ]
83
+ # }
84
+ #
85
+ # `reason` on an ignored entry is either "gitignore" or "pattern:<name>"
86
+ # (e.g. "pattern:node_modules") — never a bare boolean, so a consumer never
87
+ # has to re-derive why something was excluded.
88
+ #
89
+ # `remaining` is the generalize-list INPUT, not its output — this script
90
+ # does not attempt content-based judgement (reading file contents to guess
91
+ # "this looks like SOAP mock-ups") at all. That pass is agent judgement, per
92
+ # the Skill Implementation Principle in CLAUDE.md, and lives in
93
+ # skills/uncharted/SKILL.md's Directory Triage subsection, not here.
94
+ #
95
+ # ---------------------------------------------------------------------------
96
+ # EXIT CODES
97
+ # ---------------------------------------------------------------------------
98
+ # 0 — success (including zero candidates, or everything remaining)
99
+ # 1 — usage error: unknown flag, missing root
100
+ # 2 — input error: root does not exist or is not a directory
101
+ #
102
+ # Examples:
103
+ # directory-triage.sh . src lib vendor node_modules
104
+ # discover-subsystems.sh . | jq -r '.candidates[].path' | directory-triage.sh .
105
+ #
106
+ # Requires: bash, python3, git (optional — gitignore check is skipped with a
107
+ # notice when the root is not inside a git working tree).
108
+
109
+ set -euo pipefail
110
+
111
+ SCRIPT_NAME="directory-triage.sh"
112
+ ROOT=""
113
+ JSON_OUT=""
114
+ CANDIDATES=()
115
+
116
+ print_help() {
117
+ cat <<'EOF'
118
+ Usage: directory-triage.sh [--root DIR] [--json-out FILE] [<root>] [candidate...]
119
+ discover-subsystems.sh <root> | jq -r '.candidates[].path' | directory-triage.sh <root>
120
+
121
+ Classifies candidate directories under <root> into:
122
+ - ignored: already excluded by .gitignore, or matching a known
123
+ vendor/generated pattern (node_modules, vendor, dist, build,
124
+ .venv, venv, __pycache__, .next, coverage, target, .git,
125
+ .tox, .mypy_cache, .pytest_cache, .cache, *.egg-info)
126
+ - remaining: everything else — the input to the agent-judgement
127
+ generalize-list pass documented in skills/uncharted/SKILL.md
128
+
129
+ Options:
130
+ --root DIR Root the candidates are resolved against (or give it as
131
+ the first positional argument).
132
+ --json-out FILE Also write the JSON report to a file. stdout gets it
133
+ regardless.
134
+ -h, --help Show this help and exit 0.
135
+
136
+ Exit codes: 0 success, 1 usage error, 2 root missing/not a directory.
137
+ EOF
138
+ }
139
+
140
+ usage_error() {
141
+ echo "Usage: $SCRIPT_NAME [--root DIR] [--json-out FILE] [<root>] [candidate...]" >&2
142
+ echo " (candidates may also be piped newline-delimited on stdin)" >&2
143
+ exit 1
144
+ }
145
+
146
+ while [ "$#" -gt 0 ]; do
147
+ case "$1" in
148
+ -h|--help)
149
+ print_help
150
+ exit 0
151
+ ;;
152
+ --root)
153
+ [ "$#" -ge 2 ] || usage_error
154
+ ROOT="$2"
155
+ shift 2
156
+ ;;
157
+ --json-out)
158
+ [ "$#" -ge 2 ] || usage_error
159
+ JSON_OUT="$2"
160
+ shift 2
161
+ ;;
162
+ --)
163
+ shift
164
+ break
165
+ ;;
166
+ -*)
167
+ usage_error
168
+ ;;
169
+ *)
170
+ if [ -z "$ROOT" ]; then
171
+ ROOT="$1"
172
+ else
173
+ CANDIDATES+=("$1")
174
+ fi
175
+ shift
176
+ ;;
177
+ esac
178
+ done
179
+
180
+ # Remaining positionals after `--` are also candidates.
181
+ for arg in "$@"; do
182
+ CANDIDATES+=("$arg")
183
+ done
184
+
185
+ [ -n "$ROOT" ] || usage_error
186
+
187
+ if [ ! -d "$ROOT" ]; then
188
+ echo "$SCRIPT_NAME: root '$ROOT' does not exist or is not a directory" >&2
189
+ exit 2
190
+ fi
191
+
192
+ ROOT_ABS=$(cd -- "$ROOT" && pwd -P)
193
+
194
+ # If no candidates were given on the command line, read newline-delimited
195
+ # candidates from stdin (only when stdin is not a terminal, so an
196
+ # interactive invocation with no candidates doesn't hang waiting for input).
197
+ if [ "${#CANDIDATES[@]}" -eq 0 ] && [ ! -t 0 ]; then
198
+ while IFS= read -r line; do
199
+ [ -n "$line" ] && CANDIDATES+=("$line")
200
+ done
201
+ fi
202
+
203
+ # Determine gitignore availability once, up front.
204
+ GITIGNORE_AVAILABLE=0
205
+ if git -C "$ROOT_ABS" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
206
+ GITIGNORE_AVAILABLE=1
207
+ fi
208
+
209
+ # Fixed, path-component ignore-list. Kept as a single array so it stays easy
210
+ # to extend — see the header note above.
211
+ IGNORE_PATTERNS=(
212
+ node_modules vendor dist build .venv venv __pycache__ .next coverage
213
+ target .git .tox .mypy_cache .pytest_cache .cache
214
+ )
215
+
216
+ # ---------------------------------------------------------------------------
217
+ # Build the JSON input for the python3 classifier: one record per candidate,
218
+ # already resolved to an absolute path plus its git-ignore verdict (computed
219
+ # in bash, since `git check-ignore` is a subprocess call best kept out of the
220
+ # python3 half). Pattern matching itself happens in python3 for portable,
221
+ # component-wise path matching (os.path based) rather than a second,
222
+ # divergent bash implementation.
223
+ # ---------------------------------------------------------------------------
224
+
225
+ NOTICES=()
226
+ RECORDS_JSON="[]"
227
+
228
+ if [ "${#CANDIDATES[@]}" -gt 0 ]; then
229
+ RECORDS_TMP=$(mktemp)
230
+ trap 'rm -f "$RECORDS_TMP"' EXIT
231
+
232
+ {
233
+ for candidate in "${CANDIDATES[@]}"; do
234
+ CAND_PATH="$ROOT_ABS/$candidate"
235
+ if [ ! -e "$CAND_PATH" ]; then
236
+ NOTICES+=("candidate '$candidate' does not exist under '$ROOT_ABS' — skipped")
237
+ continue
238
+ fi
239
+ if [ ! -d "$CAND_PATH" ]; then
240
+ NOTICES+=("candidate '$candidate' is not a directory — skipped")
241
+ continue
242
+ fi
243
+ CAND_ABS=$(cd -- "$CAND_PATH" && pwd -P)
244
+
245
+ GITIGNORED="false"
246
+ if [ "$GITIGNORE_AVAILABLE" -eq 1 ]; then
247
+ if git -C "$ROOT_ABS" check-ignore -q -- "$CAND_ABS" 2>/dev/null; then
248
+ GITIGNORED="true"
249
+ fi
250
+ fi
251
+
252
+ printf '%s\t%s\t%s\n' "$candidate" "$CAND_ABS" "$GITIGNORED"
253
+ done
254
+ } > "$RECORDS_TMP"
255
+
256
+ if [ "$GITIGNORE_AVAILABLE" -eq 0 ]; then
257
+ NOTICES+=("root is not inside a git working tree — gitignore check skipped for all candidates")
258
+ fi
259
+
260
+ RECORDS_JSON=$(python3 -c '
261
+ import json, sys
262
+ records = []
263
+ with open(sys.argv[1], "r") as f:
264
+ for line in f:
265
+ line = line.rstrip("\n")
266
+ if not line:
267
+ continue
268
+ parts = line.split("\t")
269
+ if len(parts) != 3:
270
+ continue
271
+ path, abs_path, gitignored = parts
272
+ records.append({"path": path, "absolute_path": abs_path, "gitignored": gitignored == "true"})
273
+ print(json.dumps(records))
274
+ ' "$RECORDS_TMP")
275
+ fi
276
+
277
+ NOTICES_JSON=$(python3 -c '
278
+ import json, sys
279
+ print(json.dumps(sys.argv[1:]))
280
+ ' "${NOTICES[@]:-}")
281
+ # printf with an empty array above can leave one empty-string arg; strip it.
282
+ if [ "${#NOTICES[@]}" -eq 0 ]; then
283
+ NOTICES_JSON="[]"
284
+ fi
285
+
286
+ python3 -c '
287
+ import json, sys, os
288
+
289
+ root = sys.argv[1]
290
+ root_absolute = sys.argv[2]
291
+ records = json.loads(sys.argv[3])
292
+ notices = json.loads(sys.argv[4])
293
+ patterns = sys.argv[5:]
294
+
295
+ def pattern_reason(path):
296
+ # Path-COMPONENT match, never substring: split on os.sep and compare
297
+ # each component (and a suffix-glob case for *.egg-info) against the
298
+ # fixed pattern list.
299
+ components = [c for c in path.split(os.sep) if c]
300
+ for comp in components:
301
+ for pat in patterns:
302
+ if pat.startswith("*."):
303
+ suffix = pat[1:]
304
+ if comp.endswith(suffix):
305
+ return "pattern:" + pat
306
+ elif comp == pat:
307
+ return "pattern:" + pat
308
+ return None
309
+
310
+ ignored = []
311
+ remaining = []
312
+
313
+ for rec in records:
314
+ path = rec["path"]
315
+ abs_path = rec["absolute_path"]
316
+ if rec.get("gitignored"):
317
+ ignored.append({"path": path, "absolute_path": abs_path, "reason": "gitignore"})
318
+ continue
319
+ reason = pattern_reason(path)
320
+ if reason:
321
+ ignored.append({"path": path, "absolute_path": abs_path, "reason": reason})
322
+ else:
323
+ remaining.append({"path": path, "absolute_path": abs_path})
324
+
325
+ report = {
326
+ "script": "directory-triage.sh",
327
+ "version": 1,
328
+ "root": root,
329
+ "root_absolute": root_absolute,
330
+ "candidate_count": len(records),
331
+ "ignored_count": len(ignored),
332
+ "remaining_count": len(remaining),
333
+ "ignored": ignored,
334
+ "remaining": remaining,
335
+ "notices": notices,
336
+ }
337
+ print(json.dumps(report, indent=2))
338
+ ' "$ROOT" "$ROOT_ABS" "$RECORDS_JSON" "$NOTICES_JSON" "${IGNORE_PATTERNS[@]}" > "${JSON_OUT:-/dev/stdout}"
339
+
340
+ if [ -n "$JSON_OUT" ]; then
341
+ cat "$JSON_OUT"
342
+ fi