@jenga-ai/agent 4.0.0 → 4.1.1
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 +32 -1
- package/lib/skill-allow-list.json +1 -1
- package/package.json +1 -1
- package/project/app/api/scripts/capture-snapshot.js +12 -7
- package/project/app/ui/dist/assets/{index-C3oiuli_.js → index-DX2pfTAW.js} +16 -16
- package/project/app/ui/dist/index.html +1 -1
- package/scripts/verify-postinstall-reconcile.sh +33 -7
- package/skills/index/scripts/board_index.py +29 -1
- package/skills/j-do/SKILL.md +28 -15
- package/skills/j-doc/scripts/resolve_last_update.py +27 -1
- package/skills/j-todo/scripts/add_trivial_task.sh +12 -1
- package/skills/j-wtf/SKILL.md +1 -1
- package/skills/jenga/SKILL.md +49 -14
- package/skills/jenga/scripts/enrich-nl-prompt.sh +225 -0
- package/skills/jenga/scripts/load-nl-catalog.js +1 -1
- package/skills/jenga/scripts/match-playbook.sh +3 -2
- package/skills/jenga/scripts/run-playbook-step.sh +3 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Jenga AI Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DX2pfTAW.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-BADc5mmH.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -330,13 +330,27 @@ run_install "$PKG2C" "$CONSUMER_C" "$FIXTURE/case-v2.log"
|
|
|
330
330
|
|
|
331
331
|
# Detect whether this filesystem is even case-insensitive; on a case-SENSITIVE fs
|
|
332
332
|
# both names legitimately coexist and the old one is genuinely stale.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
333
|
+
#
|
|
334
|
+
# This probes the filesystem DIRECTLY rather than inferring the answer from the
|
|
335
|
+
# mirrored tree. The previous inference was circular -- it asked whether both
|
|
336
|
+
# skill.md and SKILL.md exist with DIFFERENT content, but both fixture packages
|
|
337
|
+
# above write the identical body ("# alpha"), so the inequality was false by
|
|
338
|
+
# construction and the branch concluded "case-insensitive" on every filesystem.
|
|
339
|
+
# On macOS that happened to be the right answer; on a case-SENSITIVE fs (any
|
|
340
|
+
# Linux CI runner) it was wrong, and the case-insensitive branch below then ran
|
|
341
|
+
# an assert_grep for "written-this-run" that can only hold when the two names
|
|
342
|
+
# collide -- recording a FAIL that no individual @test asserts, so only
|
|
343
|
+
# "harness reports zero failures overall" caught it. Surfaced by the E28_S17
|
|
344
|
+
# mirror staging gate's first Linux runs (2026-09-28).
|
|
345
|
+
CASE_PROBE="$FIXTURE/case-probe"
|
|
346
|
+
mkdir -p "$CASE_PROBE"
|
|
347
|
+
: > "$CASE_PROBE/probe"
|
|
348
|
+
if [ -e "$CASE_PROBE/PROBE" ]; then
|
|
338
349
|
CASE_INSENSITIVE=1
|
|
350
|
+
else
|
|
351
|
+
CASE_INSENSITIVE=0
|
|
339
352
|
fi
|
|
353
|
+
rm -rf "$CASE_PROBE"
|
|
340
354
|
|
|
341
355
|
if [ "$CASE_INSENSITIVE" -eq 1 ]; then
|
|
342
356
|
# The whole point: the skill must still exist under SOME name after the upgrade.
|
|
@@ -353,8 +367,20 @@ if [ "$CASE_INSENSITIVE" -eq 1 ]; then
|
|
|
353
367
|
assert_grep "written-this-run" "$FIXTURE/case-v2.log" \
|
|
354
368
|
"identity guard reports the spared entry as written-this-run"
|
|
355
369
|
else
|
|
356
|
-
|
|
357
|
-
|
|
370
|
+
# These names must remain PREFIX-COMPATIBLE with the case-insensitive branch
|
|
371
|
+
# above: assert_check in tests/postinstall-delete-reconciliation.bats matches
|
|
372
|
+
# with grep -F on "<tab><name>", a substring match, so a trailing "(n/a ...)"
|
|
373
|
+
# qualifier still satisfies an assertion written against the canonical name
|
|
374
|
+
# while keeping the skip visible in the harness output. The identity-guard
|
|
375
|
+
# line below already followed this convention; the two case-only-rename lines
|
|
376
|
+
# did not -- they were renamed wholesale to "case-only rename (skipped: ...)",
|
|
377
|
+
# which shares no prefix with the canonical name, so on a case-sensitive fs
|
|
378
|
+
# the "case-only rename does not delete the file the run just wrote" @test
|
|
379
|
+
# would fail with "no such check recorded by the harness". That never fired
|
|
380
|
+
# before only because the broken detection above forced every filesystem down
|
|
381
|
+
# the case-insensitive branch.
|
|
382
|
+
pass "case-only rename does not delete the just-written file (.agents) (n/a: filesystem is case-sensitive, both names legitimately coexist)"
|
|
383
|
+
pass "case-only rename does not delete the just-written file (.claude) (n/a: filesystem is case-sensitive, both names legitimately coexist)"
|
|
358
384
|
pass "identity guard reports the spared entry as written-this-run (n/a on case-sensitive fs)"
|
|
359
385
|
fi
|
|
360
386
|
|
|
@@ -6,6 +6,7 @@ import argparse
|
|
|
6
6
|
import hashlib
|
|
7
7
|
import json
|
|
8
8
|
import re
|
|
9
|
+
import subprocess
|
|
9
10
|
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any
|
|
@@ -33,7 +34,34 @@ EDGE_TYPES = {
|
|
|
33
34
|
"parent_doc",
|
|
34
35
|
}
|
|
35
36
|
FRONTMATTER_BOUNDARY = "---"
|
|
36
|
-
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _resolve_project_root() -> Path:
|
|
40
|
+
"""Locate the project root via git, not a fixed relative climb.
|
|
41
|
+
|
|
42
|
+
This script is deployed both at its source location
|
|
43
|
+
(skills/index/scripts/) and mirrored one directory deeper into
|
|
44
|
+
.claude/skills/index/scripts/ / .agents/skills/index/scripts/ -- a fixed
|
|
45
|
+
parents[N] climb from __file__ only lands on the real repo root from the
|
|
46
|
+
source location, not from a mirror. Same approach as
|
|
47
|
+
skills/j-close-story/scripts/check-story-closeable.sh,
|
|
48
|
+
check-privatized.sh:138, and compute-scope-divergence.sh:54 use for the
|
|
49
|
+
equivalent bash case.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
54
|
+
cwd=Path(__file__).resolve().parent,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
check=True,
|
|
58
|
+
)
|
|
59
|
+
return Path(result.stdout.strip())
|
|
60
|
+
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
|
61
|
+
return Path.cwd()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
ROOT_DIR = _resolve_project_root()
|
|
37
65
|
BOARD_ID_RE = re.compile(r"^E\d{2}(?:_S\d{2})?(?:_T\d{2})?$")
|
|
38
66
|
PLAN_RE = re.compile(r"^(E\d{2}_S\d{2}(?:_T\d{2})?)-plan$")
|
|
39
67
|
SUMMARY_RE = re.compile(r"^(E\d{2}_S\d{2}(?:_T\d{2})?)-summary$")
|
package/skills/j-do/SKILL.md
CHANGED
|
@@ -398,14 +398,20 @@ After resolving the task context (step 4), passing override validation (step 4.1
|
|
|
398
398
|
**If `execution_scope: inline`** (including tasks corrected above), execute the task directly in the current session without spawning a developer subagent:
|
|
399
399
|
|
|
400
400
|
1. Read the task file and load its full content (description, acceptance criteria). Do NOT create a worktree. Do NOT spawn a developer subagent.
|
|
401
|
-
2.
|
|
402
|
-
|
|
403
|
-
|
|
401
|
+
2. **Before implementation begins** — write `status: In Progress` and `date_started: <today>` to the task's frontmatter using the same file-locking protocol as `### 1.5` step 4a above:
|
|
402
|
+
1. Locate the task file: `project/board/tasks/<task_id>_*.md`.
|
|
403
|
+
2. Check for an existing lock file at `project/board/tasks/<task_id>_*.md.lock`. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked after the retry, log a warning and skip this status write (do not block task execution).
|
|
404
|
+
3. Create the lock file: write the current ISO 8601 timestamp into `project/board/tasks/<task_id>_*.md.lock`.
|
|
405
|
+
4. Update the task frontmatter fields `status: In Progress` and `date_started: <YYYY-MM-DD>` (today's date).
|
|
406
|
+
5. Delete the lock file immediately after the write completes.
|
|
407
|
+
3. Implement the task inline — make the required changes to files directly in the current session.
|
|
408
|
+
4. Run the smoke test harness before committing anything:
|
|
409
|
+
- Run `bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo node_modules/@jenga-ai/agent/scripts/smoke-harness.sh)" <changed_file>...`, passing the paths changed in step 3. With no arguments the harness infers them from `git diff --name-only HEAD`. It exits `0` on pass and `1` on failure.
|
|
404
410
|
- If neither `scripts/smoke-harness.sh` nor `node_modules/@jenga-ai/agent/scripts/smoke-harness.sh` exists, log a warning and treat the result as a pass:
|
|
405
411
|
```
|
|
406
412
|
WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
|
|
407
413
|
```
|
|
408
|
-
|
|
414
|
+
5. **If the smoke test exits non-zero**:
|
|
409
415
|
- **If this is a `--trivial`-forced run** (marker set in step 4.1.5 — and `crucial_level` is not `locked`, which never falls back, per 4.1.5's precedence note): do NOT write `status: Failed`. `--trivial` always forces `inline` with no softer "lightest safe tier" to fall back to first, so a smoke-harness failure here goes straight to the shared `#### Fallback to Full Task-Scope Pipeline` procedure below (origin: `trivial`). Do not proceed with the remaining inline steps below — the Fallback procedure takes over from here.
|
|
410
416
|
- **Otherwise** (an organically-assigned `inline` task, `--trivial` not involved): behavior is unchanged from before —
|
|
411
417
|
- Write `status: Failed` to the task's frontmatter.
|
|
@@ -414,15 +420,15 @@ After resolving the task context (step 4), passing override validation (step 4.1
|
|
|
414
420
|
INLINE TASK FAILED [<task_id>]: smoke test returned non-zero exit code. Task marked Failed. Halting.
|
|
415
421
|
```
|
|
416
422
|
- Do not commit. Do not proceed to the next task.
|
|
417
|
-
|
|
423
|
+
6. **If the smoke test passes**:
|
|
418
424
|
- Commit the changes using the standard commit convention (`task(<task_id>): <short description>`) via `/commit` in inline mode (E32_S04_T03).
|
|
419
425
|
- Run the **Intent-vs-Diff Check** (see `### 5.1. Intent-vs-Diff Check` below) for this task.
|
|
420
426
|
- Self-verify the implementation against the acceptance criteria.
|
|
421
427
|
- Write `status: Passed` and `date_completed: <today>` to the task's frontmatter if verification passes.
|
|
422
428
|
- Remove the task from `project/todo.md`.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
429
|
+
7. `inline` tasks do not invoke the tester agent — the smoke test and self-verification are the only gates.
|
|
430
|
+
8. `inline` tasks always have `needs_docs: false` — skip plan and summary documentation for the implemented task.
|
|
431
|
+
9. Continue to `### 6. Verify documentation`, then `### 7. After successful completion`.
|
|
426
432
|
|
|
427
433
|
If the implementation cannot be completed inline (scope is larger than anticipated — detected scope creep mid-run):
|
|
428
434
|
- **If this is a `--trivial`-forced run** (and `crucial_level` is not `locked`): invoke the shared `#### Fallback to Full Task-Scope Pipeline` procedure below (origin: `trivial`) — the same procedure the smoke-harness-failure branch above uses, not a second bespoke re-route.
|
|
@@ -439,24 +445,31 @@ After resolving the task context (step 4), passing override validation (step 4.1
|
|
|
439
445
|
|
|
440
446
|
`light` sits between `inline` and `task`: unlike `inline`, it spawns a real developer subagent (so it can handle small branching logic that inline's main-session execution isn't suited for); unlike `task`, it does not create a dedicated worktree and does not invoke the tester as a separate step.
|
|
441
447
|
|
|
442
|
-
1. **
|
|
448
|
+
1. **Before spawning the developer subagent** — write `status: In Progress` and `date_started: <today>` to the task's frontmatter using the same file-locking protocol as `### 1.5` step 4a above:
|
|
449
|
+
1. Locate the task file: `project/board/tasks/<task_id>_*.md`.
|
|
450
|
+
2. Check for an existing lock file at `project/board/tasks/<task_id>_*.md.lock`. If it exists and is less than 60 seconds old, wait 10 seconds and retry once. If still locked after the retry, log a warning and skip this status write (do not block task execution).
|
|
451
|
+
3. Create the lock file: write the current ISO 8601 timestamp into `project/board/tasks/<task_id>_*.md.lock`.
|
|
452
|
+
4. Update the task frontmatter fields `status: In Progress` and `date_started: <YYYY-MM-DD>` (today's date).
|
|
453
|
+
5. Delete the lock file immediately after the write completes.
|
|
443
454
|
|
|
444
|
-
2. **
|
|
455
|
+
2. **Acquire a developer concurrency slot, then spawn a developer subagent.** Acquire first, per `### 4.4. Developer Concurrency Slot Enforcement` below (`<id>` = this task's id). On a full cap (`capacity_blocked` outcome), do not spawn anything — `### 4.4` already reverts this task's status to `Pending`, logs the `capacity_blocked` event, and tracks the consecutive-block count; stop here and let `/jenga` Phase 4 retry this task on a later wave once a slot frees up. On a successful acquire, spawn the subagent (Agent tool, `subagent_type: "developer"`) with the same sender object and context payload as step 5 would use, but with an explicit instruction added to the dispatch prompt: **do not create a worktree** — implement directly against the current checkout (the session's existing working tree), not an isolated `.claude/worktrees/<slug>` copy. This is the one concrete difference from the step-5 `task` path: everything else about how the subagent implements the task (reading the task file, following acceptance criteria, following repo conventions) is unchanged.
|
|
456
|
+
|
|
457
|
+
3. **After the developer subagent reports implementation complete**, first **release the developer concurrency slot** acquired in step 2: `"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <task_id> <orchestrator_session_id>` (per `### 4.4` step 2) — this subagent's session has ended, so the slot is released now regardless of what it reports, before the smoke test result is even known. Then run the smoke test harness using the same invocation convention as `### 4.2. Inline Execution Path`:
|
|
445
458
|
- Run `bash "$([ -f scripts/smoke-harness.sh ] && echo scripts/smoke-harness.sh || echo node_modules/@jenga-ai/agent/scripts/smoke-harness.sh)" <changed_file>...`, passing the paths the subagent changed. With no arguments the harness infers them from `git diff --name-only HEAD`. It exits `0` on pass and `1` on failure.
|
|
446
459
|
- If neither `scripts/smoke-harness.sh` nor `node_modules/@jenga-ai/agent/scripts/smoke-harness.sh` exists, log a warning and treat the result as a pass:
|
|
447
460
|
```
|
|
448
461
|
WARNING [<task_id>]: smoke-harness.sh not found. Smoke test skipped (stub pass).
|
|
449
462
|
```
|
|
450
463
|
|
|
451
|
-
|
|
452
|
-
- The developer subagent self-verifies the implementation against the task's acceptance criteria. No tester subagent is invoked for a `light`-scoped task — this is a deliberate, documented exception to "the tester is the sole status-writer" (`agents/tester.md`), mirroring the same exception already established for `inline` scope in step
|
|
464
|
+
4. **If the smoke test passes**:
|
|
465
|
+
- The developer subagent self-verifies the implementation against the task's acceptance criteria. No tester subagent is invoked for a `light`-scoped task — this is a deliberate, documented exception to "the tester is the sole status-writer" (`agents/tester.md`), mirroring the same exception already established for `inline` scope in step 6 of `### 4.2`. Since no tester runs, the developer/orchestrator is the one who writes the terminal status for a `light`-scoped task.
|
|
453
466
|
- Commit the changes using the standard commit convention (`task(<task_id>): <short description>`) via `/commit`.
|
|
454
467
|
- Run the **Intent-vs-Diff Check** (see `### 5.1. Intent-vs-Diff Check` below) for this task.
|
|
455
468
|
- Write `status: Passed` and `date_completed: <today>` to the task's frontmatter if self-verification passes.
|
|
456
469
|
- Remove the task from `project/todo.md`.
|
|
457
470
|
- Continue to `### 6. Verify documentation`, then `### 7. After successful completion`.
|
|
458
471
|
|
|
459
|
-
|
|
472
|
+
5. **If the smoke test fails (non-zero exit)**: do NOT write `status: Failed` and do NOT halt. Instead, invoke `#### Fallback to Full Task-Scope Pipeline` below (origin: `light`).
|
|
460
473
|
|
|
461
474
|
#### Fallback to Full Task-Scope Pipeline
|
|
462
475
|
|
|
@@ -464,7 +477,7 @@ This is a self-contained, reusable procedure with two current callers — `### 4
|
|
|
464
477
|
|
|
465
478
|
1. **Do not mark the task `Failed`.** A smoke-harness failure (or detected scope creep) under a reduced-overhead scope means the scope was too small for the task, not that the task itself is unworkable — the correct response is to retry under full isolation, not to reject the work.
|
|
466
479
|
2. **Create a worktree** for the task, named `<E##_S##_T##-short-slug>` per standard Worktree Management conventions, if one does not already exist for this task. (A task dispatched under `light` scope, or forced `inline` via `--trivial`, never had one — both premises skip worktree creation — so this step always creates a fresh worktree in that case.)
|
|
467
|
-
3. **Acquire a developer concurrency slot** (per `### 4.4. Developer Concurrency Slot Enforcement` below, `<id>` = this task's id). This fallback spawn is a distinct developer-subagent lifecycle from whatever `light`/`trivial` attempt preceded it — that attempt's own slot, if any, was already acquired and released around it (`### 4.3` step
|
|
480
|
+
3. **Acquire a developer concurrency slot** (per `### 4.4. Developer Concurrency Slot Enforcement` below, `<id>` = this task's id). This fallback spawn is a distinct developer-subagent lifecycle from whatever `light`/`trivial` attempt preceded it — that attempt's own slot, if any, was already acquired and released around it (`### 4.3` step 2/3, or no slot at all for a `--trivial`-forced inline attempt, which never spawns a subagent) — so this step always acquires its own fresh slot. On a full cap (`capacity_blocked` outcome), do not spawn anything here either: apply `### 4.4`'s Pending-revert/log/consecutive-block handling for this task id and stop the fallback. The task remains exactly as the reduced-overhead attempt left it (any commits already made by that attempt stay in the worktree/branch just created in step 2), and `/jenga` Phase 4 retries it on a later wave once a slot frees up.
|
|
468
481
|
4. **Spawn a developer subagent** in that worktree and have it pick up from the current state of the code (the changes already made by the reduced-overhead attempt are still present in the working tree / already committed, if any commit occurred — the subagent continues from there rather than starting over).
|
|
469
482
|
5. **Invoke the tester agent** per the normal `### 5. Invoke the developer agent` flow's contract — full sender object, commit SHAs, worktree path. The tester is responsible for the terminal status write, exactly as in the standard `task`-scope pipeline. Once this developer subagent's session ends — tester-verified, failed, or errored — **release the slot** acquired in step 3: `"$([ -f scripts/release-concurrency-slot.sh ] && echo scripts/release-concurrency-slot.sh || echo node_modules/@jenga-ai/agent/scripts/release-concurrency-slot.sh)" developer <task_id> <orchestrator_session_id>` (per `### 4.4` step 2).
|
|
470
483
|
6. **Emit a clear, non-fatal fallback notice** to the user/orchestrator, using the message matching the caller's origin:
|
|
@@ -480,7 +493,7 @@ This is a self-contained, reusable procedure with two current callers — `### 4
|
|
|
480
493
|
|
|
481
494
|
### 4.4. Developer Concurrency Slot Enforcement (Shared Procedure)
|
|
482
495
|
|
|
483
|
-
This is a self-contained, reusable procedure with four current callers — the Story-Bundle Execution Mode's developer-subagent spawn (`### 1.5` step 3, one acquire for the whole bundle, never one per task inside it), the Light Execution Path's developer-subagent spawn (`### 4.3` step
|
|
496
|
+
This is a self-contained, reusable procedure with four current callers — the Story-Bundle Execution Mode's developer-subagent spawn (`### 1.5` step 3, one acquire for the whole bundle, never one per task inside it), the Light Execution Path's developer-subagent spawn (`### 4.3` step 2/3), the standard task-scope developer invocation (`### 5` below), and the `Fallback to Full Task-Scope Pipeline`'s developer-subagent spawn (step 3/5 above). Every one of these is a genuine "spawn a developer subagent" moment — `inline` scope (`### 4.2`, including a `--trivial`-forced run that hasn't yet fallen back) is the only path that never spawns a subagent and therefore never calls this procedure at all.
|
|
484
497
|
|
|
485
498
|
**`<id>`** is the task id (`E##_S##_T##`) for a single-task dispatch (the `### 5`, `### 4.3`, and Fallback call sites), or the story id (`E##_S##`) for the bundle path — one acquire call covers the whole bundle, never one per task inside it.
|
|
486
499
|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
7
|
import json
|
|
8
|
+
import subprocess
|
|
8
9
|
import sys
|
|
9
10
|
from datetime import date
|
|
10
11
|
from pathlib import Path
|
|
@@ -18,6 +19,31 @@ BOARD_DIRS = (
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _resolve_project_root() -> Path:
|
|
23
|
+
"""Locate the project root via git, not a fixed relative climb.
|
|
24
|
+
|
|
25
|
+
This script is deployed both at its source location
|
|
26
|
+
(skills/j-doc/scripts/) and mirrored one directory deeper into
|
|
27
|
+
.claude/skills/j-doc/scripts/ / .agents/skills/j-doc/scripts/ -- a fixed
|
|
28
|
+
parents[N] climb from __file__ only lands on the real repo root from the
|
|
29
|
+
source location, not from a mirror. Same approach as
|
|
30
|
+
skills/j-close-story/scripts/check-story-closeable.sh,
|
|
31
|
+
check-privatized.sh:138, and compute-scope-divergence.sh:54 use for the
|
|
32
|
+
equivalent bash case.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
37
|
+
cwd=Path(__file__).resolve().parent,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
check=True,
|
|
41
|
+
)
|
|
42
|
+
return Path(result.stdout.strip())
|
|
43
|
+
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
|
44
|
+
return Path.cwd()
|
|
45
|
+
|
|
46
|
+
|
|
21
47
|
def parse_args() -> argparse.Namespace:
|
|
22
48
|
parser = argparse.ArgumentParser(
|
|
23
49
|
description="Resolve the latest completed board date for a documentation target.",
|
|
@@ -25,7 +51,7 @@ def parse_args() -> argparse.Namespace:
|
|
|
25
51
|
parser.add_argument("target_path", help="Repo-relative documentation target path, e.g. README.md")
|
|
26
52
|
parser.add_argument(
|
|
27
53
|
"--root",
|
|
28
|
-
default=
|
|
54
|
+
default=_resolve_project_root(),
|
|
29
55
|
type=Path,
|
|
30
56
|
help="Repository root containing project/board/",
|
|
31
57
|
)
|
|
@@ -28,7 +28,18 @@
|
|
|
28
28
|
|
|
29
29
|
set -euo pipefail
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# Locate the project root via git, not a fixed relative climb — this script
|
|
32
|
+
# is deployed both at its source location (skills/j-todo/scripts/) and
|
|
33
|
+
# mirrored to .claude/skills/j-todo/scripts/ / .agents/skills/j-todo/scripts/,
|
|
34
|
+
# and a fixed "three levels up" climb lands in the wrong place from a mirror.
|
|
35
|
+
# Same approach as skills/j-close-story/scripts/check-story-closeable.sh,
|
|
36
|
+
# check-privatized.sh:138, and compute-scope-divergence.sh:54.
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
39
|
+
if [[ -z "$REPO_ROOT" ]]; then
|
|
40
|
+
echo "ERROR: could not locate project root (git rev-parse --show-toplevel failed from $SCRIPT_DIR)" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
32
43
|
cd "$REPO_ROOT"
|
|
33
44
|
|
|
34
45
|
TASKS_DIR="project/board/tasks"
|
package/skills/j-wtf/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: j.wtf
|
|
3
|
-
description: Alias of /clearify — clarifies ambiguous, dense, or under-specified prompts and conversation on request. This folder exists
|
|
3
|
+
description: Alias of /clearify — clarifies ambiguous, dense, or under-specified prompts and conversation on request. This folder exists so `j.wtf` (and its `/j-wtf` directory form) resolves to a skill; behaviour is identical to `/clearify`.
|
|
4
4
|
keywords:
|
|
5
5
|
- wtf
|
|
6
6
|
- confused
|
package/skills/jenga/SKILL.md
CHANGED
|
@@ -137,11 +137,33 @@ If all applicable rules pass (or the task is a legacy task), proceed to the next
|
|
|
137
137
|
|
|
138
138
|
This phase determines **how `/jenga` was invoked** and, for two of the four entry modes, produces a **scoped set** — a confirmed list of board IDs (epics/stories/tasks) that Phases 1-4 must restrict themselves to. All board scanning, ID parsing, cascade expansion, and rendering used by this phase already live in `skills/jenga/scripts/` per this repo's "Scripts Over Inline Logic" principle — this phase never re-implements any of that logic inline. The executing agent's job here is limited to: invoking the right script with the right arguments, relaying its STDOUT verbatim to the user when the contract calls for that, capturing the `STATE_FILE:` line from STDERR for the next turn, and forwarding the user's raw reply back into the next invocation unmodified.
|
|
139
139
|
|
|
140
|
-
**
|
|
140
|
+
**First, check for the `--enrich` flag** (`E53_S13_T01`, ported from the now-retired `/route`'s board
|
|
141
|
+
+ docs enrichment — see the Natural-language branch's step 3 and the Skill Matching & Invocation
|
|
142
|
+
Contract's Report format below for what it actually does). If the raw argument passed to `/jenga`
|
|
143
|
+
begins with the exact leading token `--enrich` followed by at least one space, strip that token
|
|
144
|
+
(and the single space after it) before anything else runs, and remember `enrichment_requested =
|
|
145
|
+
true` for the remainder of this invocation. The **remaining text** — never the original argument
|
|
146
|
+
with the flag still attached — is what every step below (including `detect-nl-intent.sh`) treats as
|
|
147
|
+
"the raw argument passed to `/jenga`"; this is what keeps the flag from ever being visible to
|
|
148
|
+
`detect-nl-intent.sh`'s `all_resolved`/`nl_intent`/`mixed` classification. If `--enrich` is not
|
|
149
|
+
present as a leading token, `enrichment_requested = false` and the argument is used as-is —
|
|
150
|
+
byte-for-byte the same behavior as before this flag existed.
|
|
151
|
+
|
|
152
|
+
`--enrich` alone (nothing after it, once whitespace is stripped) reduces to an empty remaining
|
|
153
|
+
argument — treat this exactly like no argument at all (**bare branch**); the flag has nothing to
|
|
154
|
+
enrich without free-form text and bare `/jenga` never does skill matching. `--enrich *` reduces to
|
|
155
|
+
the **wildcard branch** the same way (remaining text is the literal `*`); the wildcard branch also
|
|
156
|
+
never does skill matching, so `enrichment_requested` is simply never consulted there. Concretely,
|
|
157
|
+
`enrichment_requested` is only ever read in the **natural-language branch** below, and only once
|
|
158
|
+
that branch's own step 3 confirms a single-skill match — it is inert everywhere else. Passing
|
|
159
|
+
`--enrich` on a scoped (`<ids>`) argument is likewise a no-op: the scoped branch does not perform
|
|
160
|
+
skill matching either, so there is nothing for the flag to attach to.
|
|
161
|
+
|
|
162
|
+
**Then, determine the invocation form** from the remaining argument (if any) passed to `/jenga`:
|
|
141
163
|
|
|
142
164
|
- No argument at all → **bare branch**.
|
|
143
165
|
- The argument is the literal string `*` → **wildcard branch**.
|
|
144
|
-
- Any other non-empty argument → invoke `skills/jenga/scripts/detect-nl-intent.sh "<
|
|
166
|
+
- Any other non-empty argument → invoke `skills/jenga/scripts/detect-nl-intent.sh "<remaining argument>"` (E53_S01_T01) and branch on its `classification` field:
|
|
145
167
|
- `all_resolved` or `mixed` → **scoped branch** (below) — this is the same branch as before; only its internal mechanics changed (see below).
|
|
146
168
|
- `nl_intent` → **natural-language branch** (below) — new for E53_S01, no new sigil or entry point, purely a new outcome of this same argument-shape detection.
|
|
147
169
|
|
|
@@ -170,7 +192,12 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
|
|
|
170
192
|
|
|
171
193
|
1. **Load the catalog** — invoke `skills/jenga/scripts/load-nl-catalog.sh` with no arguments (E53_S01_T02). Its stdout is the full skill catalog (`name`/`description`/`keywords`/`examples`/`prefered_agent` per skill), sourced exclusively from `lib/generate-skill-allow-list.js`'s generated inventory — see the script's own header for the full contract. Never re-derive this catalog by re-scanning `skills/` inline.
|
|
172
194
|
2. **Match** — run this section's own **Skill Matching & Invocation Contract** (below) — the three-pass keyword → example-similarity → description match, including its tie-break and no-match handling — against this catalog, treating `detect-nl-intent.sh`'s `raw_argument` field as the prompt.
|
|
173
|
-
3. **Confident single match** —
|
|
195
|
+
3. **Confident single match** — if `enrichment_requested` is `true` (the `--enrich` flag was passed, per Phase 0.75's preamble above), first invoke `skills/jenga/scripts/enrich-nl-prompt.sh "<raw_argument>"` (`E53_S13_T01`, ported from `/route`'s Steps 3-5) and assemble the enriched composite message from its JSON output, in this exact order:
|
|
196
|
+
- **Part A — Matched skill (full content)** — the matched skill's full `SKILL.md` body (everything after its YAML front-matter), wrapped as `<!-- SKILL: /<matched-skill-name> --> ... <!-- END SKILL -->`.
|
|
197
|
+
- **Part B — Board & documentation context** — a `## 📋 Relevant Board Context` list (one line per `board_items` entry: `- [<status>] **<id>** — <title> (\`<file>\`)`) followed by a `## 📄 Relevant Documentation` list (one line per `docs` entry: `` - `<path>` — <summary> ``). Omit either sub-list entirely (not an empty heading) when its array is empty.
|
|
198
|
+
- **Part C — Original prompt** — `## 🗣 Original Prompt` followed by the raw prompt, verbatim, in a blockquote.
|
|
199
|
+
|
|
200
|
+
Then report the routing decision using the **Skill Matching & Invocation Contract**'s **Report** format (including its `Board items found`/`Docs found` lines, populated from `enrich-nl-prompt.sh`'s `board_items_found`/`docs_found` fields, since `enrichment_requested` is `true` here), and invoke the matched skill exactly as the **Invoke** rule already does — but deliver the enriched composite message as the working input instead of the raw prompt when enrichment ran. When `enrichment_requested` is `false` (the default, unflagged path — unchanged from before this flag existed), skip `enrich-nl-prompt.sh` entirely: report using the **Report** format's default (no `Board items found`/`Docs found` lines) and invoke the matched skill directly with the raw prompt, exactly as before. Either way: load `agents/<prefered_agent>.md` when the matched skill specifies `metadata.prefered_agent`, otherwise execute the skill instructions directly. The matched skill's own execution takes over from here — do not continue into this `/jenga` invocation's Phase 1.
|
|
174
201
|
4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing the **Skill Matching & Invocation Contract**'s generic disambiguation options, attempt a **playbook fallback** (E53_S02): invoke `skills/jenga/scripts/match-playbook.sh "<raw_argument>"`. This step only ever runs when step 3 above did NOT already commit to a confident single-skill match — a confident single-skill match always wins outright and this playbook fallback is never even invoked in that case. Branch on `match-playbook.sh`'s `classification` field:
|
|
175
202
|
- `playbook_match` → continue to **step 5 (Playbook proposal and execution)** below.
|
|
176
203
|
- `ambiguous` or `no_match` → continue to **step 6 (Fall through to the Skill Matching & Invocation Contract's disambiguation)** below — the exact behavior this branch already had before E53_S02, unchanged.
|
|
@@ -217,14 +244,11 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
|
|
|
217
244
|
|
|
218
245
|
##### Skill Matching & Invocation Contract
|
|
219
246
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
`skills/j-route/SKILL.md`'s own Step 2/6/7 carry the authoritative copy of this same contract for
|
|
226
|
-
`/route`'s own use — the two are intentionally duplicated for public-mirror reasons; keep them in sync
|
|
227
|
-
by hand if either changes.
|
|
247
|
+
`/jenga` is the **sole owner** of this contract (`E53_S13_T02`). `/route` — which previously carried
|
|
248
|
+
an independent, hand-synced copy of this same three-pass matching/tie-break/report logic in its own
|
|
249
|
+
Step 2/6/7 — has been retired outright (hard break, no shim; see `E53_S13`'s story). There is no
|
|
250
|
+
other copy of this contract anywhere in the codebase to keep in sync with; if you change the matching
|
|
251
|
+
logic here, this section is the only place that needs updating.
|
|
228
252
|
|
|
229
253
|
**Matching** (three passes, stop at first confident match):
|
|
230
254
|
|
|
@@ -269,9 +293,20 @@ Routing to: /<matched-skill-name>
|
|
|
269
293
|
Reason: <one sentence explaining why this skill was chosen>
|
|
270
294
|
```
|
|
271
295
|
|
|
272
|
-
(
|
|
273
|
-
|
|
274
|
-
|
|
296
|
+
**`Board items found`/`Docs found` (opt-in, `E53_S13_T01`)** — these two lines are appended to the
|
|
297
|
+
Report above, in this order, **only** when the natural-language branch's `enrichment_requested` is
|
|
298
|
+
`true` (i.e. the caller passed `--enrich`, per Phase 0.75's preamble):
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
Board items found: <count>
|
|
302
|
+
Docs found: <count>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`<count>` is `enrich-nl-prompt.sh`'s `board_items_found`/`docs_found` field respectively (the total
|
|
306
|
+
match count before the top-5/top-3 cap, not the number of items actually listed in the enriched
|
|
307
|
+
prompt's Part B). When `enrichment_requested` is `false` — the default path, unchanged from before
|
|
308
|
+
this flag existed — neither line is emitted; the Report is exactly the two lines above and nothing
|
|
309
|
+
more.
|
|
275
310
|
|
|
276
311
|
Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (the
|
|
277
312
|
tie-break above already handled that).
|