@jenga-ai/agent 1.2.4 → 1.3.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 (42) hide show
  1. package/README.md +1 -0
  2. package/agents/developer.md +18 -0
  3. package/agents/scrum-master.md +1 -0
  4. package/agents/tester.md +18 -0
  5. package/hooks/on_session_end.sh +27 -0
  6. package/package.json +18 -17
  7. package/skills/commit/SKILL.md +11 -1
  8. package/skills/dev-done/SKILL.md +46 -0
  9. package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
  10. package/skills/init/SKILL.md +7 -6
  11. package/skills/init/assets/scope-thresholds_template.json +7 -0
  12. package/skills/init/scripts/init.sh +6 -0
  13. package/skills/publish/SKILL.md +66 -0
  14. package/skills/publish/adapters/npm-ci.md +34 -0
  15. package/skills/publish/adapters/npm.md +18 -0
  16. package/skills/publish/assets/ci-contract.md +27 -0
  17. package/skills/publish/assets/publish.example.json +27 -0
  18. package/skills/publish/schemas/publish.schema.json +20 -0
  19. package/skills/publish/scripts/npm_ci_pipeline.sh +29 -0
  20. package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
  21. package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
  22. package/skills/publish/scripts/publish_common.sh +16 -0
  23. package/skills/publish/scripts/show_history.sh +12 -5
  24. package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
  25. package/skills/publish/scripts/write_ledger_entry.sh +92 -2
  26. package/skills/reconcile/SKILL.md +121 -11
  27. package/skills/reconcile/assets/report_format.md +17 -0
  28. package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
  29. package/skills/uncharted/SKILL.md +200 -21
  30. package/skills/uncharted/scripts/directory-triage.sh +342 -0
  31. package/skills/uncharted/scripts/elicitation-state.sh +457 -0
  32. package/templates/SCRUM_BOARD_SCHEMA.md +57 -0
  33. package/templates/agent-context.md.tpl +23 -11
  34. package/templates/copilot-instructions.md.tpl +21 -11
  35. package/mcp/router/README.md +0 -19
  36. package/mcp/router/embedder.js +0 -23
  37. package/mcp/router/index.js +0 -204
  38. package/mcp/router/matcher.js +0 -87
  39. package/mcp/router/package-lock.json +0 -1048
  40. package/mcp/router/package.json +0 -11
  41. package/mcp/router/skill-index.js +0 -104
  42. package/skills/route/SKILL.md +0 -180
package/README.md CHANGED
@@ -228,6 +228,7 @@ Skills live in `.agents/skills/<name>/SKILL.md`. Invoke with `/<name>` in your A
228
228
  | `/do` | Execute tasks from the scrum board, drives the Developer agent through the full loop |
229
229
  | `/dooo` | Parallel execution orchestrator — runs multiple tasks simultaneously via sub-agents |
230
230
  | `/redo` | Rework a previous implementation by commit SHA or Epic/Story number |
231
+ | `/publish` | Configure, validate, and orchestrate scaffolded release workflows — `setup`, `deploy`, `stage` (npm/npm-ci pre-approval staged publishing), `history`, `release-notes` |
231
232
  | `/error` | Guided troubleshooting — gathers context, investigates, and drives a fix |
232
233
  | `/train` | Scaffold and run ML training jobs (new job from template or run existing) |
233
234
 
@@ -287,6 +287,24 @@ See `templates/PROBLEM_RAPPORT_TEMPLATE.md` for the required format. Commit the
287
287
 
288
288
  ---
289
289
 
290
+ ## Investigative Mode
291
+
292
+ **Trigger.** You are sometimes dispatched not to implement a task, but purely to build understanding of existing code — e.g. by the scrum-master during `/uncharted`'s conversational architecture elicitation, when it needs to know what a named flow or target actually does before proposing graph nodes or asking the user to confirm/correct an understanding. This is a distinct dispatch mode from the standard Task Intake flow above, and it is recognized by the request itself (you are asked to *trace* or *investigate*, not to *implement*), not by any board field.
293
+
294
+ **Hard constraints.** Investigative Mode is strictly read-only:
295
+ - No worktree is created for write purposes, no application code is written or modified, no dependency installs or generated artifacts.
296
+ - No commits of any kind.
297
+ - No board status writes — task/story/epic status is the tester's exclusive responsibility, and Investigative Mode doesn't touch the board at all, not even a status you'd normally be permitted to leave alone.
298
+ - No edits to `PROJECT_SUMMARY.md`, board files, or any other project artifact. The only output is the trace itself, returned to whoever dispatched you.
299
+
300
+ **Sandbox — reuse the existing worktree hooks, read-only.** Per the story decision behind this mode (see `project/documentation/plans/uncharted-interactive-elicitation.md` and its solution assessment, Problem 7 / Solution A), do not invent a new isolation mechanism. Mount a throwaway worktree via the same `WorktreeCreate` hook used for normal tasks (see Worktree Management above) purely to get a disposable, isolated checkout to read from, and tear it down via `WorktreeRemove` once the investigation ends. This is convention-enforced, not filesystem-enforced: the hook mounts an ordinary writable worktree, and it is Investigative Mode's contract — not a permission bit — that keeps it read-only. Treat any temptation to write into that worktree (a scratch file, a quick local test run) as a violation of the mode, not a harmless side effect.
301
+
302
+ **What you trace.** For the named flow or target, follow what the code actually does: call paths, data flow, key decision points, error handling, and any conditions or configuration that change the behavior. Report this in plain language back to the dispatcher — this is not a new artifact type and is not written to disk as part of Investigative Mode itself; if the dispatching flow later decides the finding is worth persisting, that happens through its own normal mechanism (e.g. a graph write or a summary doc), not through you.
303
+
304
+ **Human-oracle-availability limitation.** For genuinely undocumented code, there is often no reliable code-level way to confirm what was *intended* — only what currently happens. Naming conventions can mislead, a branch that looks dead may be load-bearing for a caller you haven't found, and "this must be for X" is a guess dressed as a finding. When you hit this wall, say so explicitly — report the uncertainty, name what you did and didn't check, and stop short of presenting a guess as a settled fact. This is an accepted, standing limitation of the mode, not something to engineer around by fabricating confidence.
305
+
306
+ ---
307
+
290
308
  ## Hooks
291
309
 
292
310
  Defined in agent frontmatter:
@@ -76,6 +76,7 @@ This is a self-contained procedure, not a session-start-only step. It may be inv
76
76
  6. **This is the only path** by which a mid-task agent request results in a `crucial_level` board write. Developer and tester never write `crucial_level`, `crucial_set_by`, or `crucial_note` directly to a board file themselves under any circumstance — they may only *request* the change via a `crucial_escalation` rapport, and the actual frontmatter write happens here, exclusively by scrum-master, closing the loop described in E39's Purpose section ("the actual frontmatter write still goes through scrum-master, never the subagent itself").
77
77
  - `status_review`: Review the scrum board for any tasks or stories whose status should be updated based on recent activity.
78
78
  - `story_rollup`: Check all tasks under the referenced story; if all are `Passed` or `Passed with remarks`, update the story status to `Passed` (or `Passed with remarks` if any remark exists). Then check epic rollup (see Rollup Logic).
79
+ - `elicitation_resume`: A `/uncharted` conversational architecture elicitation session (`onboard`'s default flow, or `segment --mode investigate` — E20_S08_T03) ended mid-run without converging. Read `state_file` (`project/queue/elicitation-state/<elicitation_id>.json`, written by `skills/uncharted/scripts/elicitation-state.sh`) to see exactly where it left off — which nodes already converged, which are still pending or flagged, and any directory-triage/checkpoint data already confirmed — then resume the conversational flow documented in `skills/uncharted/SKILL.md`'s Multi-Session Persistence subsection from that point rather than restarting the elicitation from scratch. If the state file is missing or unreadable, report that to the user rather than silently starting a fresh elicitation under the same id.
79
80
  - After processing all triggers, **clear the file** by writing an empty file — do not leave processed triggers.
80
81
 
81
82
  2. **Check `project/queue/project_summary_updates.jsonl`** — If non-empty, review each proposed update and apply, revise, or reject it with a short note. Clear the file after processing.
package/agents/tester.md CHANGED
@@ -407,6 +407,24 @@ There is no default analytics run. Analytics only happen when explicitly scoped
407
407
 
408
408
  ---
409
409
 
410
+ ## Investigative Mode
411
+
412
+ **Trigger.** You are sometimes dispatched not to validate a task's implementation, but purely to build understanding of what the existing test suite actually covers for a named flow or target — e.g. by the scrum-master during `/uncharted`'s conversational architecture elicitation, alongside the developer's Investigative Mode pass over the same flow. This is a distinct dispatch mode from the standard Sender Object / Task Intake / Status Management flow above, recognized by the request itself (you are asked to *trace coverage*, not to *validate a task*), not by any board field.
413
+
414
+ **Hard constraints.** Investigative Mode is strictly read-only:
415
+ - No worktree is created for write purposes, no test files are written or modified, no test runs that mutate state, no dependency installs.
416
+ - No commits of any kind.
417
+ - No board status writes — this mode doesn't touch task/story/epic status at all, even though status writes are ordinarily your exclusive responsibility.
418
+ - No edits to `PROJECT_SUMMARY.md`, board files, or any other project artifact. The only output is the trace itself, returned to whoever dispatched you.
419
+
420
+ **Sandbox — reuse the existing worktree hooks, read-only.** Per the story decision behind this mode (see `project/documentation/plans/uncharted-interactive-elicitation.md` and its solution assessment, Problem 7 / Solution A), do not invent a new isolation mechanism. Mount a throwaway worktree via the same `WorktreeCreate` hook the developer agent uses for normal tasks, purely to get a disposable, isolated checkout to read from, and tear it down via `WorktreeRemove` once the investigation ends. This is convention-enforced, not filesystem-enforced: the hook mounts an ordinary writable worktree, and it is Investigative Mode's contract — not a permission bit — that keeps it read-only. Do not execute the test suite in a way that writes fixtures, snapshots, or coverage artifacts back into that worktree; reading existing test files and existing coverage output (if already present) is the mode's ceiling.
421
+
422
+ **What you trace — the distinct vantage point.** Where the developer's Investigative Mode pass traces what the code *does*, yours traces what the test suite *actually exercises and verifies* for that same flow: which tests touch it, what they assert (and what they merely execute without asserting), and where the coverage gap is — untested branches, unasserted side effects, error paths with no test at all, or a flow that "passes" only because nothing checks the part that matters. These are two distinct vantage points on the same flow, not two names for the same read; do not simply restate the developer's trace with "and there's a test for it" appended.
423
+
424
+ **Human-oracle-availability limitation.** The same limitation the developer faces applies to you, with an added facet: a passing test suite doesn't clarify intent either — a test can be green because it correctly verifies the right behavior, or green because it asserts nothing meaningful, and the test's own docstring or name can be as misleading as the code's. When you cannot determine from the tests (or their absence) what the intended behavior actually is, say so explicitly — report the uncertainty and the specific gap you couldn't close, rather than presenting a guess as a settled coverage verdict. This is an accepted, standing limitation of the mode, not something to engineer around by fabricating confidence.
425
+
426
+ ---
427
+
410
428
  ## Hooks
411
429
 
412
430
  Defined in agent frontmatter:
@@ -308,6 +308,33 @@ for HANDOFF_FILE in "$HANDOFF_DIR"/*.json; do
308
308
  echo "$TRIGGER" >> "$DEV_QUEUE"
309
309
  echo "[on_session_end] scrum-master → developer queue: implementation_assignment"
310
310
  fi
311
+
312
+ # A conversational architecture elicitation session (/uncharted
313
+ # onboard's default flow, or segment --mode investigate — E20_S08_T03)
314
+ # ended mid-run without converging. The session driving it is
315
+ # responsible for calling skills/uncharted/scripts/elicitation-state.sh
316
+ # pause and then writing this handoff with status "elicitation_paused"
317
+ # as its last action (see skills/uncharted/SKILL.md's Multi-Session
318
+ # Persistence subsection). This routes that pause into a resume
319
+ # signal for the next scrum-master session, per the existing
320
+ # SessionEnd/queue pattern rather than a new persistence mechanism
321
+ # (solution-assessment-uncharted-interactive-elicitation.md, Problem 11).
322
+ if [ "$HANDOFF_STATUS" = "elicitation_paused" ]; then
323
+ TRIGGER=$(jq -n \
324
+ --slurpfile h "$HANDOFF_FILE" \
325
+ --arg type "elicitation_resume" \
326
+ --arg date "$TIMESTAMP" \
327
+ '{
328
+ type: $type,
329
+ date: $date,
330
+ sender: { agent: "scrum-master", session_id: $h[0].session_id, date: $date },
331
+ elicitation_id: ($h[0].elicitation_id // ""),
332
+ state_file: ($h[0].state_file // ""),
333
+ message: "A conversational architecture elicitation session paused mid-run. Resume it from the persisted state file."
334
+ }')
335
+ echo "$TRIGGER" >> "$QUEUE_FILE"
336
+ echo "[on_session_end] scrum-master (elicitation_paused) → scrum-master queue: elicitation_resume"
337
+ fi
311
338
  ;;
312
339
 
313
340
  developer)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jenga-ai/agent",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
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.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -16,12 +16,12 @@
16
16
  "postinstall": "node scripts/postinstall.js",
17
17
  "test": "bats tests/*.bats",
18
18
  "validate:npm-metadata": "bash scripts/validate_npm_metadata.sh",
19
- "ui:dev": "npm run ui:dev --prefix project/app",
20
- "ui:build": "npm run ui:build --prefix project/app",
21
- "api:start": "npm run api:start --prefix project/app",
22
- "api:dev": "npm run api:dev --prefix project/app",
23
- "dashboard:start": "npm run dashboard:start --prefix project/app",
24
- "dashboard:open": "npm run dashboard:open --prefix project/app"
19
+ "ui:dev": "npm run ui:dev --prefix project/app --",
20
+ "ui:build": "npm run ui:build --prefix project/app --",
21
+ "api:start": "npm run api:start --prefix project/app --",
22
+ "api:dev": "npm run api:dev --prefix project/app --",
23
+ "dashboard:start": "npm run dashboard:start --prefix project/app --",
24
+ "dashboard:open": "npm run dashboard:open --prefix project/app --"
25
25
  },
26
26
  "files": [
27
27
  "skills/",
@@ -31,7 +31,17 @@
31
31
  "templates/",
32
32
  "bin/",
33
33
  "lib/",
34
- "mcp/",
34
+ "mcp/execute-ticket/*.js",
35
+ "mcp/execute-ticket/package.json",
36
+ "mcp/help/*.js",
37
+ "mcp/help/package.json",
38
+ "mcp/router/*.js",
39
+ "mcp/router/package.json",
40
+ "mcp/router/package-lock.json",
41
+ "mcp/router/README.md",
42
+ "mcp/training_runner/*.js",
43
+ "mcp/training_runner/package.json",
44
+ "mcp/training_runner/package-lock.json",
35
45
  "README.md",
36
46
  "LICENSE"
37
47
  ],
@@ -67,15 +77,6 @@
67
77
  "url": "https://knappkod.se/jenga-ai"
68
78
  },
69
79
  "license": "MIT",
70
- "dependencies": {
71
- "@huggingface/transformers": "^4.2.0"
72
- },
73
- "overrides": {
74
- "sharp": "^0.34.5"
75
- },
76
- "comments": {
77
- "audit": "sharp <0.35.0 and adm-zip <0.6.0 are transitive deps of @huggingface/transformers with no upstream fix available. Not exploitable in this context: only text feature-extraction pipeline is used — no image processing or ZIP handling at application boundary. Review when @huggingface/transformers ships a patched release."
78
- },
79
80
  "devDependencies": {
80
81
  "bats": "^1.13.0"
81
82
  }
@@ -36,7 +36,17 @@ If `--inline` is absent **and** `JENGA_COMMIT_INLINE` is not set (or is not `1`)
36
36
 
37
37
  If no epic, task, or story has been implemented, exit with the message: "No implementation to commit."
38
38
 
39
- 1. **Reconcile first** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
39
+ 1. **Reconcile first, scoped to this commit** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
40
+ - **Determine the scope to pass** (in order):
41
+ 1. If `/commit` was invoked with an explicit epic/story/task id argument (e.g. `/commit E46`, `/commit E17_S04_T02`), use that id.
42
+ 2. Otherwise, if the calling context already identifies a single task/story/epic just completed (e.g. a developer or tester agent's sender object naming `task_id`/`story_id`/`epic_id`, or a `/do` invocation for one task), use that id.
43
+ 3. Otherwise, derive it from what's staged: run `git diff --cached --name-only` and filter to paths under `project/board/epics/`, `project/board/stories/`, and `project/board/tasks/`. Extract the id each matched filename encodes.
44
+ - If every extracted id shares a single common story-or-narrower ancestor (all belong to one story's own file plus any of its own tasks' files, or are all exactly one task's own file, or are all exactly one epic's own file with nothing narrower staged), use that single most-specific id (story/task id if present, else the epic id) as the scope.
45
+ - If matched ids span more than one distinct story, or more than one distinct epic, this case does not resolve — do not guess between them; fall through to case 4.
46
+ - **If no board files at all are staged, skip `/reconcile` entirely — do not fall through to case 4.** A commit that touches no board file cannot itself commit board drift, so there is nothing for a pre-commit reconcile to protect against; running a full unscoped scan here would be pure waste, not a safety net. This is distinct from the multi-item case immediately above, which still falls through to case 4 since board files genuinely are changing there.
47
+ 4. Otherwise — no argument, no sender context, and staged board files span more than one distinct story or epic — fall back to unscoped `/reconcile` (full board), unchanged from prior behavior.
48
+ - **Invoke** `/reconcile <scope>` when a scope was determined in cases 1-3, unscoped `/reconcile` on the case-4 fallback, or skip the reconcile step entirely per case 3's no-board-files-staged outcome. Do not re-derive or duplicate `/reconcile`'s own default-scope-to-epic expansion here — passing a bare story or task id through is sufficient; `/reconcile` itself resolves that to the containing epic.
49
+ - **If `/reconcile` was skipped** (case 3's no-board-files-staged outcome) — continue silently to the next step, exactly as the no-drift outcome below.
40
50
  - **If reconcile detects and corrects drift** — inform the user what changed (e.g. demoted/promoted statuses, merged orphaned worktrees, cleaned `todo.md` entries) before proceeding.
41
51
  - **If reconcile finds no drift** — continue silently to the next step.
42
52
 
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: dev-done
3
+ description: Commit the current work and immediately sync it into the .claude/ and .agents/ mirrors. Shortcut that chains /commit followed by /self-sync.
4
+ keywords:
5
+ - dev done
6
+ - commit and sync
7
+ - commit and mirror
8
+ - done syncing
9
+ examples:
10
+ - "dev-done E42_S04_T01"
11
+ - "commit this and sync the mirrors"
12
+ ---
13
+
14
+ # Dev-Done — Commit, then Sync the Mirrors
15
+
16
+ Chains `/commit <scope-id>` and `/self-sync`, the same "convenience shortcut" pattern `skills/lgtm/SKILL.md`
17
+ uses for `/commit` + `/continue` — applied here to the commit -> mirror-sync sequence instead of the
18
+ commit -> next-task sequence. Useful right after implementing a root-level framework change
19
+ (`skills/`, `agents/`, `hooks/`, `scripts/`, `templates/`, `settings.json`), so the `.claude/` and
20
+ `.agents/` mirrors never sit stale waiting on a manual `/self-sync` call.
21
+
22
+ The deterministic decision of whether `/commit` halted early (nothing to commit) or completed
23
+ normally lives in `skills/dev-done/scripts/classify-commit-outcome.sh`, not inline here — see that
24
+ script's header for the exact contract.
25
+
26
+ ## Instructions
27
+
28
+ 1. Invoke the `/commit` skill with whatever scope-id argument `/dev-done` itself was given (e.g.
29
+ `/dev-done E42_S04_T01` invokes `/commit E42_S04_T01`) — the same EST scope-id argument contract
30
+ `/commit` already accepts (epic, story, or task id; see `skills/commit/SKILL.md`). Capture its full
31
+ output text.
32
+
33
+ 2. Pass the captured output to the classifier script:
34
+ ```
35
+ skills/dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
36
+ ```
37
+
38
+ 3. If the script exits `1` (HALT): print its stdout — the exact message `No implementation to
39
+ commit.` — to the user, and stop. Do **not** invoke `/self-sync`.
40
+
41
+ 4. If the script exits `0` (PROCEED): invoke the `/self-sync` skill and wait for it to finish. This
42
+ happens regardless of whether `/commit` reported drift or doc-sync findings along the way — those
43
+ are informational, not blocking (see `skills/commit/SKILL.md`).
44
+
45
+ 5. Report both steps' output to the user in sequence — the commit result first, then the self-sync
46
+ summary.
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/dev-done/scripts/classify-commit-outcome.sh
4
+ #
5
+ # Deterministic classifier backing `/dev-done` (story E42_S04, task
6
+ # E42_S04_T01). `/dev-done` chains `/commit <scope-id>` then `/self-sync`,
7
+ # but must NOT proceed to `/self-sync` when `/commit` halted early because
8
+ # there was nothing to commit. Per CLAUDE.md's "Skill Implementation
9
+ # Principle — Scripts Over Inline Logic", that halt-vs-proceed check is a
10
+ # deterministic, repeatable text match — it does not belong as inline
11
+ # conditional logic in `skills/dev-done/SKILL.md`, so it lives here instead,
12
+ # the same way `skills/self-sync/SKILL.md` delegates its own filesystem work
13
+ # to `skills/self-sync/scripts/run.js` rather than inlining it.
14
+ #
15
+ # `/commit` and `/self-sync` are themselves agent-executed skills, not plain
16
+ # executables — this script never shells out to invoke either one. It only
17
+ # classifies text that the calling agent already captured from `/commit`'s
18
+ # output. Invoking `/commit` and `/self-sync` remains `SKILL.md`'s job.
19
+ #
20
+ # ---------------------------------------------------------------------------
21
+ # WHAT IT MATCHES
22
+ # ---------------------------------------------------------------------------
23
+ # `skills/commit/SKILL.md`'s Instructions section states, verbatim:
24
+ #
25
+ # "If no epic, task, or story has been implemented, exit with the message:
26
+ # "No implementation to commit.""
27
+ #
28
+ # That exact sentence — "No implementation to commit." — is the ONLY halt
29
+ # signal this script recognizes. If a future edit to `skills/commit/SKILL.md`
30
+ # changes that wording, this script's match must be updated to match, or it
31
+ # will silently stop recognizing the halt case (see Dependencies & Risks in
32
+ # the task's execution plan).
33
+ #
34
+ # ---------------------------------------------------------------------------
35
+ # INVOCATION
36
+ # ---------------------------------------------------------------------------
37
+ # classify-commit-outcome.sh [<captured-commit-output>]
38
+ #
39
+ # The text `/commit` produced is passed either as the single argument, or
40
+ # (when no argument is given) read from stdin in full, e.g.:
41
+ #
42
+ # skills/dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
43
+ # printf '%s' "$COMMIT_OUTPUT" | skills/dev-done/scripts/classify-commit-outcome.sh
44
+ #
45
+ # ---------------------------------------------------------------------------
46
+ # OUTPUT CONTRACT (stdout, single line, nothing else)
47
+ # ---------------------------------------------------------------------------
48
+ # HALT case stdout is the exact literal message "No implementation to
49
+ # commit." — the calling agent relays this stdout verbatim
50
+ # to the user and does NOT invoke `/self-sync`.
51
+ # PROCEED case stdout is the literal token "PROCEED" — the calling
52
+ # agent invokes `/self-sync` next.
53
+ #
54
+ # ---------------------------------------------------------------------------
55
+ # EXIT CODES
56
+ # ---------------------------------------------------------------------------
57
+ # 0 PROCEED — `/commit` completed normally (regardless of whether it
58
+ # reported drift or doc-sync findings along the way); stdout is
59
+ # "PROCEED".
60
+ # 1 HALT — `/commit` halted early with the exact "No implementation to
61
+ # commit." message; stdout is that exact message.
62
+ # 2 Usage/environment error (e.g. no input at all — neither an argument
63
+ # nor anything on stdin); nothing meaningful on stdout, an error on
64
+ # stderr.
65
+ # ---------------------------------------------------------------------------
66
+
67
+ set -euo pipefail
68
+
69
+ HALT_MESSAGE="No implementation to commit."
70
+
71
+ if [ "$#" -gt 1 ]; then
72
+ echo "Usage: $(basename "$0") [<captured-commit-output>]" >&2
73
+ echo " (or pipe the captured output on stdin with no argument)" >&2
74
+ exit 2
75
+ fi
76
+
77
+ if [ "$#" -eq 1 ]; then
78
+ INPUT="$1"
79
+ else
80
+ # No argument — read the full captured output from stdin.
81
+ if [ -t 0 ]; then
82
+ echo "ERROR: no argument given and stdin is a terminal — nothing to classify" >&2
83
+ exit 2
84
+ fi
85
+ INPUT="$(cat)"
86
+ fi
87
+
88
+ if [ -z "$INPUT" ]; then
89
+ echo "ERROR: no commit output provided to classify" >&2
90
+ exit 2
91
+ fi
92
+
93
+ # Trim leading/trailing whitespace from the whole input for the exact-match case.
94
+ TRIMMED="$(printf '%s' "$INPUT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
95
+
96
+ if [ "$TRIMMED" = "$HALT_MESSAGE" ]; then
97
+ printf '%s\n' "$HALT_MESSAGE"
98
+ exit 1
99
+ fi
100
+
101
+ # Also match the halt message appearing as its own standalone line anywhere
102
+ # in a longer captured response (e.g. wrapped in surrounding agent prose),
103
+ # since the calling agent's captured "output text" is free-form, not a
104
+ # guaranteed exact-equals string.
105
+ while IFS= read -r line; do
106
+ LINE_TRIMMED="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
107
+ if [ "$LINE_TRIMMED" = "$HALT_MESSAGE" ]; then
108
+ printf '%s\n' "$HALT_MESSAGE"
109
+ exit 1
110
+ fi
111
+ done <<< "$INPUT"
112
+
113
+ printf 'PROCEED\n'
114
+ exit 0
@@ -128,12 +128,13 @@ This script handles all scaffolding in one step:
128
128
  4. Creates `project/PROJECT_SUMMARY.md` with placeholder content
129
129
  5. Creates `project/configs/workflow.json` with shared constants
130
130
  6. Creates `project/configs/test-config.json` stub
131
- 7. Creates `project/data/baselines.json`
132
- 8. Creates `project/logs/events.json`
133
- 9. Creates `docs/STRATEGY.md` — a strategic brief stub intended for investors, partners, and the product team
134
- 10. Creates `CHANGELOG.md` from the shared template — a running log of notable changes, seeded with an `[Unreleased]` section
135
- 11. Applies the chosen visibility mode via `scripts/apply-project-visibility.sh`, which records it as `project_files_visibility` in `jenga.config.json` and performs any `.gitignore` change
136
- 12. Stages and commits all files with the message `init: scaffold project structure and workflow config`
131
+ 7. Creates `project/configs/scope-thresholds.json` with default execution-scope thresholds (consumed by `/jenga` and `/do`, which halt if it's missing)
132
+ 8. Creates `project/data/baselines.json`
133
+ 9. Creates `project/logs/events.json`
134
+ 10. Creates `docs/STRATEGY.md` — a strategic brief stub intended for investors, partners, and the product team
135
+ 11. Creates `CHANGELOG.md` from the shared template — a running log of notable changes, seeded with an `[Unreleased]` section
136
+ 12. Applies the chosen visibility mode via `scripts/apply-project-visibility.sh`, which records it as `project_files_visibility` in `jenga.config.json` and performs any `.gitignore` change
137
+ 13. Stages and commits all files with the message `init: scaffold project structure and workflow config`
137
138
 
138
139
  The visibility mode is validated before any scaffolding happens, so an invalid
139
140
  value fails fast and leaves nothing behind. It is applied before the commit, so
@@ -0,0 +1,7 @@
1
+ {
2
+ "threshold_version": 1,
3
+ "inline_max_files": 1,
4
+ "inline_max_lines": 20,
5
+ "story_max_files": 5,
6
+ "bundle_lock_ttl_minutes": 30
7
+ }
@@ -67,6 +67,12 @@ cp "$ASSETS_DIR/workflow_template.json" project/configs/workflow.json
67
67
  echo "→ Copying test-config.json from template..."
68
68
  cp "$ASSETS_DIR/test-config_template.json" project/configs/test-config.json
69
69
 
70
+ # ─── 6.5. Create project/configs/scope-thresholds.json ───────────────────────
71
+ # Consumed by skills/jenga (Phase 0) and skills/do (Step 0); both halt if it's
72
+ # missing, so it must exist immediately after scaffold.
73
+ echo "→ Copying scope-thresholds.json from template..."
74
+ cp "$ASSETS_DIR/scope-thresholds_template.json" project/configs/scope-thresholds.json
75
+
70
76
  # ─── 7. Create project/data/baselines.json ───────────────────────────────────
71
77
  echo "→ Creating baselines.json..."
72
78
  echo '{}' > project/data/baselines.json
@@ -9,6 +9,8 @@ keywords:
9
9
  - npm
10
10
  - registry
11
11
  - release notes
12
+ - staged publishing
13
+ - stage
12
14
  examples:
13
15
  - "publish setup --target staging-appstore"
14
16
  - "publish setup --type mobile-ios"
@@ -21,6 +23,9 @@ examples:
21
23
  - "publish deploy --target my-droplet --dry-run"
22
24
  - "publish history --limit 5"
23
25
  - "publish release-notes --target staging-appstore"
26
+ - "test a release before publishing"
27
+ - "publish stage --target npm-registry --dry-run"
28
+ - "approve a staged npm release"
24
29
  metadata:
25
30
  scope: multi-target-v2
26
31
  primary_target: multi
@@ -76,6 +81,7 @@ If config or env validation fails, the skill exits with code `4` and does not co
76
81
  |---|---|---|---|
77
82
  | `/publish setup` | Prepare or refresh target configuration | `skills/publish/scripts/setup_wizard.sh` | Supported types: `mobile-ios`, `npm`, `npm-ci`, `droplet` |
78
83
  | `/publish deploy` | Run the full 11-step deploy orchestration | `skills/publish/scripts/publish_deploy.sh` | Dispatches to the adapter for the target's `type` (`mobile-ios`, `npm`, `npm-ci`, or `droplet`); `--dry-run` is honoured end-to-end |
84
+ | `/publish stage` | Stage an npm release into npm's staged-publishing area, smoke-test it in isolation, then approve or reject it | `skills/publish/scripts/npm_stage_pipeline.sh` (the `publish` sub-command) and `skills/publish/scripts/npm_stage_inspect.sh` (`list`, `view`, `download`, `test`, `approve`, `reject`) | Supported for `npm` and `npm-ci` target types only |
79
85
  | `/publish history` | Read the canonical publish ledger | `skills/publish/scripts/show_history.sh` | Target-agnostic; filter by `--target <name>` |
80
86
  | `/publish release-notes` | Merge new release notes into the standing `CHANGELOG.md` (or a standalone draft via `--output`) without publishing | `skills/publish/scripts/generate_release_notes.sh` | Target-agnostic |
81
87
 
@@ -170,6 +176,66 @@ Example invocations:
170
176
 
171
177
  Use `--dry-run` to rehearse a release, validate that gates pass, and confirm the generated release notes without publishing.
172
178
 
179
+ ### `/publish stage`
180
+
181
+ Staged publishing for `npm`/`npm-ci` targets only. Not available for
182
+ `mobile-ios` or `droplet` — those adapters have no staging area to dispatch
183
+ to. The lifecycle is **stage → test → approve**, with `reject` as the
184
+ discard path at any point after staging:
185
+
186
+ ```text
187
+ staged ──▶ stage_tested ──▶ approved (approve requires 2FA; refuses without
188
+ │ │ a passing test unless --force <reason>)
189
+ │ │
190
+ └─────────────┴──▶ rejected (discard path — available from any staged state)
191
+ ```
192
+
193
+ Two registry constraints trip people up and are worth stating plainly:
194
+
195
+ - **The package must already exist on the registry.** npm's staged-publishing
196
+ workflow only applies to a package that has had at least one normal,
197
+ non-staged publish already — `validate_npm_stage_env.sh` checks this
198
+ up front and exits `4` if it doesn't hold.
199
+ - **Approval requires 2FA; staging does not.** `npm stage publish` (the
200
+ `stage` sub-command below) can run non-interactively in CI. `npm stage
201
+ approve` cannot — npmjs.com requires an interactive one-time-password
202
+ step for approval, so it is always a human action even when staging was
203
+ automated (see the npm-ci adapter's OIDC staging section for the CI-stage,
204
+ human-approve split).
205
+
206
+ All seven sub-commands:
207
+
208
+ ```text
209
+ /publish stage publish <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]
210
+ /publish stage test <stage-id> [--keep] [--config <path>] [--dry-run] [--json]
211
+ /publish stage list [<package-spec>] [--config <path>] [--dry-run] [--json]
212
+ /publish stage view <stage-id> [--config <path>] [--dry-run] [--json]
213
+ /publish stage download <stage-id> [--out <dir>] [--config <path>] [--dry-run] [--json]
214
+ /publish stage approve <stage-id> [--otp <otp>] [--force <reason>] [--config <path>] [--dry-run] [--json]
215
+ /publish stage reject <stage-id> [--config <path>] [--dry-run] [--json]
216
+ ```
217
+
218
+ Implementation:
219
+
220
+ - `publish` → `bash skills/publish/scripts/npm_stage_pipeline.sh <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]` — six ordered phases (validate, gates, pack, stage, capture, ledger); writes a `staged` ledger entry on success.
221
+ - `test`, `list`, `view`, `download`, `approve`, `reject` → `bash skills/publish/scripts/npm_stage_inspect.sh <sub> [args] [--config <path>] [--dry-run] [--json]`
222
+ - `test` downloads the exact staged tarball into an isolated scratch directory outside the repo, installs it, runs the target's `npm.stage.smoke_cmd` (or the documented default check), and writes a `stage_tested` ledger entry (`pass`/`fail`).
223
+ - `approve` refuses to run unless a passing `test` is on record for that exact stage id, unless `--force <reason>` is given; writes an `approved` ledger entry.
224
+ - `reject` is the discard path omitted from npm's own staged-publishing docs page — documented here so it stays discoverable; writes a `rejected` ledger entry.
225
+
226
+ Example invocations:
227
+
228
+ ```bash
229
+ /publish stage publish npm-registry ./publish.json --dry-run
230
+ /publish stage test <stage-id>
231
+ /publish stage approve <stage-id>
232
+ /publish stage reject <stage-id>
233
+ ```
234
+
235
+ Use `/publish stage` to rehearse and smoke-test a release before it goes
236
+ live — the point of staging is that a bad tarball is caught in isolation
237
+ instead of being caught by users after `npm publish`.
238
+
173
239
  ### `/publish history`
174
240
 
175
241
  ```text
@@ -190,6 +190,40 @@ A successful non-dry-run produces:
190
190
  - A history entry in `project/logs/publish-history.json` written by
191
191
  `publish_deploy.sh` with `platform_state: "triggered"`
192
192
 
193
+ ## Staged Publishing
194
+
195
+ This target type also supports **staged publishing** via `/publish stage` —
196
+ npm's pre-publish staging area — with an OIDC-specific split between the
197
+ automated and human halves of the flow:
198
+
199
+ - **The actual `npm stage publish --provenance` call always runs inside
200
+ GitHub Actions, never on a local machine.** For `npm-ci` targets, phase 4
201
+ of `npm_stage_pipeline.sh` (run locally via `/publish stage publish`)
202
+ dispatches the `stage` job of the target's generated workflow (the same
203
+ `<workflow_path>` / OIDC Trusted Publisher link `npm_ci_pipeline.sh` uses
204
+ for `/publish deploy`, generated with a `mode: publish | stage`
205
+ `workflow_dispatch` input) via
206
+ `gh workflow run <workflow_filename> --repo <github_repo> -f mode=stage`,
207
+ then blocks on `gh run watch` until that run finishes. The `stage` job
208
+ itself runs a plain `npm stage publish --provenance --access <access>
209
+ --tag <dist_tag>` step, authorised through the same Trusted Publisher OIDC
210
+ link described above — no `NPM_TOKEN` is needed to stage, exactly as none
211
+ is needed to publish. Local `--dry-run` only prints the `gh workflow run`
212
+ command that would be dispatched; it never triggers a workflow run.
213
+ - **Approval is always a human, out-of-CI step.** `npm stage approve`
214
+ requires an interactive npm 2FA one-time password — there is no OIDC
215
+ equivalent for approval. A human runs
216
+ `bash skills/publish/scripts/npm_stage_inspect.sh test <stage-id>` (or
217
+ relies on the automated CI-staged test result) and then
218
+ `bash skills/publish/scripts/npm_stage_inspect.sh approve <stage-id> --otp <otp>`
219
+ from their own machine. `reject` (same script) is available to either
220
+ side to discard a staged candidate.
221
+
222
+ Same registry-existence precondition as a normal `npm-ci` deploy: staged
223
+ publishing only applies to a package that has already had at least one
224
+ non-staged publish (`validate_npm_stage_env.sh` checks this up front, exit
225
+ `4` if not).
226
+
193
227
  ## Post-deploy manual steps
194
228
 
195
229
  After a successful deploy trigger, the adapter prints the workflow run URL.
@@ -109,6 +109,24 @@ A successful run produces:
109
109
  - A local (and optionally pushed) git tag `v<version>` matching the published `version`
110
110
  - A history row appended to `project/logs/publish-history.json`
111
111
 
112
+ ## Staged Publishing
113
+
114
+ Before a live `deploy`, this target type also supports **staged publishing**
115
+ via `/publish stage` — npm's own pre-publish staging area, which lets a
116
+ release be smoke-tested from the exact tarball that would ship before it
117
+ becomes visible on the registry. See `skills/publish/SKILL.md`'s
118
+ `### /publish stage` section for the full command reference; summary here:
119
+
120
+ - `bash skills/publish/scripts/npm_stage_pipeline.sh <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]` runs validate → gates → pack → stage → capture → ledger and writes a `staged` ledger entry.
121
+ - `bash skills/publish/scripts/npm_stage_inspect.sh test <stage-id>` installs the staged tarball into an isolated scratch directory and smoke-tests it, writing a `stage_tested` ledger entry.
122
+ - `bash skills/publish/scripts/npm_stage_inspect.sh approve <stage-id>` requires an npm 2FA one-time password and refuses without a passing `test` on record unless `--force <reason>` is given.
123
+ - `bash skills/publish/scripts/npm_stage_inspect.sh reject <stage-id>` discards the staged version.
124
+
125
+ Same registry-existence precondition as a normal `npm` publish: staged
126
+ publishing only applies to a package that has already had at least one
127
+ non-staged publish (`validate_npm_stage_env.sh` checks this up front, exit
128
+ `4` if not).
129
+
112
130
  ## Post-deploy manual steps
113
131
 
114
132
  After a successful publish, the deploy flow must print:
@@ -71,6 +71,33 @@ These inputs remain optional in non-interactive mode:
71
71
  | `3` | Deploy failure | Used by the iOS adapter pipeline for build/export/upload failures |
72
72
  | `4` | Config or environment invalid | Implemented by validation scripts |
73
73
 
74
+ ## Staged publishing (`/publish stage`) — exit codes and ledger states
75
+
76
+ `/publish stage` (npm/npm-ci only) has its own exit-code contract, separate
77
+ from the deploy contract above:
78
+
79
+ | Code | Meaning |
80
+ |---|---|
81
+ | `0` | Success (staged, or `--dry-run`/`--help` completed cleanly) |
82
+ | `1` | User declined the pack confirmation prompt (or no tty was available) — `npm_stage_pipeline.sh` only |
83
+ | `2` | A mandatory pre-deploy gate failed; nothing was staged — `npm_stage_pipeline.sh` only |
84
+ | `3` | The underlying `npm stage <sub>` command failed, the tarball install failed, or the smoke test failed (`npm_stage_inspect.sh`); or `npm stage publish`/stage-id capture failed (`npm_stage_pipeline.sh`) |
85
+ | `4` | Config/environment validation failure (`validate_npm_stage_env.sh`, or bad args), or (`npm_stage_inspect.sh` only) a usage error, an unknown sub-command, or the `approve` test interlock refusing without a passing test on record and no `--force <reason>` |
86
+
87
+ `test` (via `npm_stage_inspect.sh`) surfaces a smoke-test failure as a
88
+ non-zero exit — code `3` — distinct from a usage error (`4`).
89
+
90
+ The publish ledger (`project/logs/publish-history.json`) gains four new
91
+ `platform_state` values alongside the existing `uploaded`/`partial`/
92
+ `dry-run`/`failed`:
93
+
94
+ | `platform_state` | Written by | Meaning |
95
+ |---|---|---|
96
+ | `staged` | `npm_stage_pipeline.sh` | Version staged to npm's staged-publishing area; not yet visible on the registry |
97
+ | `stage_tested` | `npm_stage_inspect.sh test` | The staged tarball was installed in isolation and smoke-tested; ledger row records `pass`/`fail` |
98
+ | `approved` | `npm_stage_inspect.sh approve` | The staged version was approved (2FA-gated) and is now published |
99
+ | `rejected` | `npm_stage_inspect.sh reject` | The staged version was discarded and will never be published |
100
+
74
101
  ## Precedence rules
75
102
 
76
103
  1. Command-line flags win over environment variables.
@@ -80,6 +80,33 @@
80
80
  "provider_short_name": "exampleco"
81
81
  },
82
82
  "notes": "Production upload example using App Store export mode."
83
+ },
84
+ {
85
+ "name": "npm-ci-example",
86
+ "type": "npm-ci",
87
+ "platform": "npm-ci-oidc",
88
+ "github_repo": "exampleco/example-package",
89
+ "workflow_path": ".github/workflows/npm-publish.yml",
90
+ "checks": {
91
+ "pre": [
92
+ "lint",
93
+ "type-check"
94
+ ],
95
+ "post": [
96
+ "smoke-test"
97
+ ]
98
+ },
99
+ "npm": {
100
+ "package_name": "@exampleco/example-package",
101
+ "access": "public",
102
+ "registry": "https://registry.npmjs.org",
103
+ "dist_tag": "latest",
104
+ "stage": {
105
+ "smoke_cmd": "npm run smoke-test",
106
+ "require_test_before_approve": true
107
+ }
108
+ },
109
+ "notes": "Scaffold example only. Demonstrates the optional npm.stage block used by '/publish stage'. Replace package_name and github_repo with your real values."
83
110
  }
84
111
  ]
85
112
  }