@jenga-ai/agent 3.5.0 → 4.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 (136) hide show
  1. package/README.md +85 -78
  2. package/agents/developer.md +1 -1
  3. package/agents/scrum-master.md +20 -2
  4. package/agents/tester.md +3 -3
  5. package/hooks/on_session_end.sh +5 -5
  6. package/lib/generate-agent-context.js +2 -2
  7. package/lib/generate-copilot-hooks.js +1 -1
  8. package/lib/generate-skill-allow-list.js +79 -8
  9. package/lib/mirror.js +1 -1
  10. package/lib/postinstall-manifest.js +1 -1
  11. package/lib/skill-allow-list.json +2 -2
  12. package/mcp/help/index.js +8 -17
  13. package/mcp/help/scan.js +73 -0
  14. package/package.json +5 -1
  15. package/project/app/api/lib/resolve-project-root.js +1 -1
  16. package/project/app/api/parsers/knowledge-graph.js +100 -9
  17. package/project/app/api/routes/health.js +36 -0
  18. package/project/app/api/scripts/capture-snapshot.js +9 -6
  19. package/project/app/package.json +4 -0
  20. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  21. package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
  22. package/project/app/ui/dist/index.html +2 -2
  23. package/project/app/ui/package.json +4 -0
  24. package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
  25. package/scripts/acquire-concurrency-slot.sh +35 -5
  26. package/scripts/apply-j-prefix.sh +46 -5
  27. package/scripts/build-pages-site.sh +1 -1
  28. package/scripts/check-public-playbook-steps.sh +158 -52
  29. package/scripts/check-publicignore-match.sh +2 -2
  30. package/scripts/compute-deploy-reconcile.sh +5 -5
  31. package/scripts/delete-bare-skill-dirs.sh +329 -0
  32. package/scripts/generate-legacy-shipped-paths.js +2 -2
  33. package/scripts/idea_manager.sh +273 -3
  34. package/scripts/mark-deployed.sh +2 -2
  35. package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
  36. package/scripts/populate-knowledge-graph.js +213 -5
  37. package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
  38. package/scripts/postinstall.js +1 -1
  39. package/scripts/release-concurrency-slot.sh +34 -4
  40. package/scripts/render-ranked-list.sh +270 -0
  41. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  42. package/scripts/repoint-skill-refs.sh +539 -0
  43. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  44. package/scripts/strip-polyfill-frontmatter.py +166 -0
  45. package/scripts/todo_manager.sh +16 -1
  46. package/scripts/validate-typed-object.sh +750 -0
  47. package/scripts/verify-legacy-seed-reconcile.sh +10 -10
  48. package/scripts/verify-postinstall-reconcile.sh +7 -7
  49. package/scripts/write-context-digest.sh +1 -1
  50. package/skills/j-brainstorm/SKILL.md +3 -4
  51. package/skills/j-btw/SKILL.md +3 -4
  52. package/skills/j-clearify/SKILL.md +5 -6
  53. package/skills/j-close-story/SKILL.md +11 -12
  54. package/skills/j-close-story/scripts/check-privatized.sh +4 -4
  55. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  56. package/skills/j-commit/SKILL.md +3 -4
  57. package/skills/j-continue/SKILL.md +5 -6
  58. package/skills/j-deep-dive/SKILL.md +3 -4
  59. package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
  60. package/skills/j-distribute/SKILL.md +3 -4
  61. package/skills/j-do/SKILL.md +100 -18
  62. package/skills/j-doc/SKILL.md +3 -4
  63. package/skills/j-doc-sync/SKILL.md +4 -4
  64. package/skills/j-dooo/SKILL.md +6 -15
  65. package/skills/j-error/SKILL.md +3 -4
  66. package/skills/j-evaluate/SKILL.md +3 -4
  67. package/skills/j-examplify/SKILL.md +3 -4
  68. package/skills/j-gitignore/SKILL.md +157 -0
  69. package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
  70. package/skills/j-gitignore/scripts/_catalog.sh +105 -0
  71. package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
  72. package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
  73. package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
  74. package/skills/j-help/SKILL.md +3 -4
  75. package/skills/j-idea/SKILL.md +80 -9
  76. package/skills/j-idea/assets/idea_template.md +1 -1
  77. package/skills/j-improve/SKILL.md +4 -5
  78. package/skills/j-init/SKILL.md +23 -14
  79. package/skills/j-init/assets/scope-thresholds_template.json +5 -2
  80. package/skills/j-init/scripts/apply-scaffold-visibility.sh +9 -7
  81. package/skills/j-init/scripts/init.sh +4 -4
  82. package/skills/j-jbp/SKILL.md +3 -4
  83. package/skills/j-lgtm/SKILL.md +3 -4
  84. package/skills/j-pi-plan/SKILL.md +3 -4
  85. package/skills/j-playbook/SKILL.md +1 -1
  86. package/skills/j-proceed/SKILL.md +3 -4
  87. package/skills/j-publish/SKILL.md +4 -5
  88. package/skills/j-publish/adapters/npm-ci.md +6 -1
  89. package/skills/j-publish/adapters/npm.md +1 -1
  90. package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
  91. package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
  92. package/skills/j-publish/scripts/run_gates.sh +1 -1
  93. package/skills/j-reconcile/SKILL.md +40 -7
  94. package/skills/j-reconcile/assets/report_format.md +11 -0
  95. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +13 -13
  96. package/skills/j-reconcile-origin/SKILL.md +3 -4
  97. package/skills/j-redo/SKILL.md +4 -5
  98. package/skills/j-skillify/SKILL.md +3 -4
  99. package/skills/j-spinoff/SKILL.md +4 -5
  100. package/skills/j-status/SKILL.md +18 -4
  101. package/skills/j-todo/SKILL.md +44 -5
  102. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  103. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  104. package/skills/j-uncharted/SKILL.md +252 -13
  105. package/skills/j-uncharted/scripts/detect-dependencies.sh +80 -22
  106. package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
  107. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  108. package/skills/j-uncharted/scripts/elicitation-state.sh +1 -1
  109. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  110. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  111. package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
  112. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  113. package/skills/j-wtf/SKILL.md +4 -5
  114. package/skills/jenga/SKILL.md +106 -11
  115. package/skills/jenga/playbooks/board-hygiene.json +32 -0
  116. package/skills/jenga/playbooks/schema.json +73 -6
  117. package/skills/jenga/playbooks/understand-then-commit.json +19 -0
  118. package/skills/jenga/scripts/load-nl-catalog.js +5 -2
  119. package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
  120. package/skills/jenga/scripts/load-playbooks.sh +290 -5
  121. package/skills/jenga/scripts/match-playbook.sh +4 -4
  122. package/skills/jenga/scripts/run-playbook-step.sh +267 -1
  123. package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
  124. package/templates/SKILL_TEMPLATE.md +12 -0
  125. package/templates/permission-levels/level-1-locked.json +1 -1
  126. package/templates/permission-levels/level-2-guarded.json +1 -1
  127. package/templates/permission-levels/level-3-standard.json +1 -1
  128. package/templates/permission-levels/level-4-elevated.json +2 -2
  129. package/templates/permission-levels/level-5-unrestricted.json +2 -2
  130. package/templates/playbook-types.json +40 -6
  131. package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
  132. package/project/app/ui/dist/assets/index-CdK3Qrep.css +0 -1
  133. package/scripts/audit-twin-divergence.sh +0 -625
  134. package/scripts/generate-j-alias.sh +0 -333
  135. package/skills/j-dev-done/SKILL.md +0 -53
  136. package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
@@ -181,6 +181,18 @@
181
181
  # stdout). If that was the last step, emits a completion report instead (see OUTPUT SCHEMA)
182
182
  # and removes the state file.
183
183
  #
184
+ # RUNTIME TYPE VERIFICATION (E62_S02_T01): when a value IS given, it is checked against the
185
+ # CURRENT step's declared `output_types` (read off that step's own SKILL.md), via
186
+ # `scripts/validate-typed-object.sh` -- normalizing first, then re-checking, before any
187
+ # failure is declared. A `{when, type}` conditional declaration conforms if it satisfies AT
188
+ # LEAST ONE branch (no classifier is ever run, no branch state is tracked). A value that
189
+ # still does not conform after normalize is NOT stored -- this call is silently redirected
190
+ # into the exact same halt behavior as `advance <state_file> failed "<note>"` (see below),
191
+ # with a note naming the step, the declared type(s) checked, and the raw pre-normalization
192
+ # value. A step with no declared `output_types` (including one this call cannot resolve a
193
+ # SKILL.md for) is completely unaffected: no lookup is even attempted beyond determining
194
+ # there is nothing to check.
195
+ #
184
196
  # run-playbook-step.sh advance <state_file> skipped
185
197
  # Records the CURRENT step as SKIPPED (a new `skipped` list, distinct from `completed`) and
186
198
  # advances the pointer exactly like `passed` -- a skipped step never halts the chain and is
@@ -597,6 +609,30 @@ elif [ "$SUBCOMMAND" = "advance" ]; then
597
609
 
598
610
  PROJECT_DIR="$(resolve_project_dir)"
599
611
 
612
+ # --- E62_S02_T01: resolve the jenga-agent PACKAGE root, needed ONLY to look up a completing
613
+ # step's declared `output_types` off its SKILL.md for runtime type verification. Mirrors
614
+ # load-playbooks.sh's own PKG_ROOT resolution (monorepo checkout vs. installed npm package) --
615
+ # including reusing JENGA_PLAYBOOKS_TEST_ROOT verbatim as the SAME test-injection override, rather
616
+ # than inventing a second env var for the same concept. Deliberately gated on
617
+ # `OUTCOME = passed && EXTRA non-empty`: those are the ONLY two conditions under which
618
+ # verification does anything at all (see the no-op cases in this task's board item), so a
619
+ # `skipped`/`failed` call, or a `passed` call with no typed-output value, never pays for this
620
+ # resolution at all -- "no lookup even attempted beyond determining there is nothing to check."
621
+ PKG_ROOT=""
622
+ if [ "$OUTCOME" = "passed" ] && [ -n "$EXTRA" ]; then
623
+ ADVANCE_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
624
+ if [ -n "${JENGA_PLAYBOOKS_TEST_ROOT:-}" ]; then
625
+ PKG_ROOT="$JENGA_PLAYBOOKS_TEST_ROOT"
626
+ elif [ -d "$ADVANCE_SCRIPT_DIR/../../../templates" ]; then
627
+ PKG_ROOT="$(cd "$ADVANCE_SCRIPT_DIR/../../.." && pwd)"
628
+ elif [ -d "$PROJECT_DIR/node_modules/@jenga-ai/agent/templates" ]; then
629
+ PKG_ROOT="$PROJECT_DIR/node_modules/@jenga-ai/agent"
630
+ else
631
+ echo "Error: could not locate the jenga-agent package root (templates/ not found via monorepo checkout or node_modules/@jenga-ai/agent) -- cannot resolve output_types for step verification." >&2
632
+ exit 2
633
+ fi
634
+ fi
635
+
600
636
  PY_SCRIPT="$(mktemp -t run-playbook-step-advance-XXXXXX.py)"
601
637
  trap 'rm -f "$PY_SCRIPT"' EXIT
602
638
 
@@ -604,7 +640,9 @@ elif [ "$SUBCOMMAND" = "advance" ]; then
604
640
  import json
605
641
  import os
606
642
  import re
643
+ import subprocess
607
644
  import sys
645
+ import tempfile
608
646
  from datetime import datetime, timezone
609
647
 
610
648
  state_file_path = sys.argv[1]
@@ -615,6 +653,221 @@ outcome = sys.argv[2]
615
653
  extra = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] != "" else None
616
654
  note = extra
617
655
  project_dir = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] != "" else None
656
+ # E62_S02_T01: the jenga-agent package root, resolved (or hard-errored) by the bash wrapper above,
657
+ # ONLY when outcome == "passed" and extra is non-empty. Empty string in every other case -- exactly
658
+ # the cases where verification is a documented no-op, so this is never even looked at.
659
+ pkg_root = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] != "" else None
660
+
661
+ # ---------------------------------------------------------------------------------------------
662
+ # E62_S02_T01: runtime type verification at the "passed" chokepoint
663
+ # ---------------------------------------------------------------------------------------------
664
+ # `extract_output_types`/`declared_type_entries` below are a DELIBERATE, DOCUMENTED duplication of
665
+ # skills/jenga/scripts/load-playbooks.sh's own `extract_types_field`/`extract_output_types`/
666
+ # `declared_type_entries` (see that script's "TYPE REGISTRY" section, around line 788 as of
667
+ # E62_S01_T04). The task considered factoring this into a shared, importable module, but every
668
+ # python body in both scripts is a throwaway file generated into a fresh `mktemp` path per
669
+ # invocation (see the `cat > "$PY_SCRIPT" <<'PY'` pattern this whole file uses) -- there is no
670
+ # existing precedent in this repo for a shared importable python module fed to that pattern, and
671
+ # building one purely for a ~30-line, rarely-changing frontmatter parser was judged riskier than a
672
+ # small, clearly-labelled duplicate. If this parser ever needs a THIRD copy, that is the signal to
673
+ # extract it for real; until then, a change to one copy's parsing rules should always be checked
674
+ # against the other by anyone touching either.
675
+ _FRONTMATTER_RE = re.compile(r'^---\r?\n(.*?)\r?\n---', re.DOTALL)
676
+
677
+
678
+ def extract_output_types(skill_md_path):
679
+ """Best-effort extraction of the `output_types` frontmatter field from a SKILL.md. Returns
680
+ None if the file/field is missing or unparseable, a `str` for the single-static-type form, or
681
+ a `list[dict]` for the `{when, type}` list form. Mirrors load-playbooks.sh's
682
+ `extract_types_field(path, "output_types")` exactly -- see the module-level note above."""
683
+ try:
684
+ with open(skill_md_path, encoding="utf-8") as fh:
685
+ content = fh.read()
686
+ except OSError:
687
+ return None
688
+
689
+ fm_match = _FRONTMATTER_RE.match(content)
690
+ if not fm_match:
691
+ return None
692
+
693
+ fm_lines = fm_match.group(1).splitlines()
694
+
695
+ for i, line in enumerate(fm_lines):
696
+ key_match = re.match(r'^output_types:\s*(.*)$', line)
697
+ if not key_match:
698
+ continue
699
+
700
+ rest = key_match.group(1).strip()
701
+ if rest:
702
+ return rest.strip('"\'')
703
+
704
+ # Block/list form: gather subsequent, more-indented lines into a list of dicts.
705
+ items = []
706
+ current = {}
707
+ j = i + 1
708
+ while j < len(fm_lines):
709
+ raw_line = fm_lines[j]
710
+ if not raw_line.strip():
711
+ j += 1
712
+ continue
713
+ if not raw_line[0].isspace():
714
+ break # a new top-level frontmatter key ends this block
715
+
716
+ stripped = raw_line.strip()
717
+ item_match = re.match(r'^-\s*(.*)$', stripped)
718
+ if item_match:
719
+ if current:
720
+ items.append(current)
721
+ current = {}
722
+ remainder = item_match.group(1)
723
+ kv = re.match(r'^([a-zA-Z_]+):\s*(.*)$', remainder) if remainder else None
724
+ if kv:
725
+ current[kv.group(1)] = kv.group(2).strip().strip('"\'')
726
+ else:
727
+ kv = re.match(r'^([a-zA-Z_]+):\s*(.*)$', stripped)
728
+ if kv:
729
+ current[kv.group(1)] = kv.group(2).strip().strip('"\'')
730
+ j += 1
731
+
732
+ if current:
733
+ items.append(current)
734
+ return items if items else None
735
+
736
+ return None
737
+
738
+
739
+ def declared_type_entries(value):
740
+ """Normalize an `output_types` value into `[(type_or_None, when_or_None), ...]`. Mirrors
741
+ load-playbooks.sh's `declared_type_entries` exactly -- see the module-level note above."""
742
+ if not value:
743
+ return []
744
+ if isinstance(value, str):
745
+ return [(value, None)]
746
+ if isinstance(value, list):
747
+ entries = []
748
+ for item in value:
749
+ if isinstance(item, dict):
750
+ entries.append((item.get("type") or None, item.get("when") or None))
751
+ else:
752
+ entries.append((None, None))
753
+ return entries
754
+ return []
755
+
756
+
757
+ def verify_typed_output(step_name, raw_value, pkg_root_dir):
758
+ """The E62_S02_T01 chokepoint check itself.
759
+
760
+ Returns `(failure_note_or_None, resolved_value)`:
761
+ - `(None, value)` -- conforms (or nothing declared/checkable -- a no-op). `value` is the
762
+ raw value unless a declared branch needed normalization to conform,
763
+ in which case it is that branch's normalized value.
764
+ - `(note, None)` -- every declared branch is non-conforming even after normalize. `note`
765
+ names the step, the declared type(s) checked, and the raw
766
+ pre-normalization value, for the existing `failed` -> `halted` path.
767
+
768
+ The conditional `{when, type}` rule (ratified at epic level, not re-derived here): the value
769
+ conforms if it satisfies AT LEAST ONE declared branch. No classifier script is ever run and no
770
+ branch state is tracked -- every declared type is checked against the SAME captured value.
771
+ """
772
+ skill_md_path = os.path.join(pkg_root_dir, "skills", step_name, "SKILL.md")
773
+ if not os.path.isfile(skill_md_path):
774
+ # No resolvable skill dir (e.g. a synthetic/test step name, or a stale board reference) --
775
+ # nothing to check against. Not this chokepoint's problem to diagnose further.
776
+ return None, raw_value
777
+
778
+ declared = extract_output_types(skill_md_path)
779
+ entries = [e for e in declared_type_entries(declared) if e[0] is not None]
780
+ if not entries:
781
+ # No declared output_types (or only malformed entries, which is a load-time concern this
782
+ # chokepoint does not re-litigate) -- a documented no-op, exactly as today.
783
+ return None, raw_value
784
+
785
+ validator = os.path.join(pkg_root_dir, "scripts", "validate-typed-object.sh")
786
+ if not os.path.isfile(validator):
787
+ print(
788
+ f"Error: type registry validator not found at {validator} -- cannot verify step "
789
+ f"'{step_name}'s declared output_types.",
790
+ file=sys.stderr,
791
+ )
792
+ sys.exit(2)
793
+
794
+ types_checked = []
795
+ branch_reasons = []
796
+ normalized_fallback = None # first branch that conforms only after normalize
797
+
798
+ for type_name, when_val in entries:
799
+ if type_name not in types_checked:
800
+ types_checked.append(type_name)
801
+
802
+ tmp_path = None
803
+ try:
804
+ with tempfile.NamedTemporaryFile(
805
+ mode="w", encoding="utf-8", delete=False, prefix="run-playbook-step-verify-"
806
+ ) as tmp:
807
+ tmp.write(raw_value)
808
+ tmp_path = tmp.name
809
+ proc = subprocess.run(
810
+ [validator, "--value-file", tmp_path, type_name],
811
+ capture_output=True,
812
+ text=True,
813
+ )
814
+ finally:
815
+ if tmp_path is not None:
816
+ try:
817
+ os.unlink(tmp_path)
818
+ except OSError:
819
+ pass
820
+
821
+ if proc.returncode == 0:
822
+ # Conforms as given, under this branch alone -- the strongest possible verdict.
823
+ # "At least one branch accepts it" is satisfied immediately; stop checking further
824
+ # branches and never report this as normalized.
825
+ return None, raw_value
826
+ if proc.returncode == 2:
827
+ if normalized_fallback is None:
828
+ try:
829
+ payload = json.loads(proc.stdout)
830
+ normalized_fallback = payload.get("value")
831
+ except (json.JSONDecodeError, AttributeError):
832
+ normalized_fallback = raw_value
833
+ continue
834
+ if proc.returncode == 3:
835
+ reason = None
836
+ try:
837
+ payload = json.loads(proc.stdout)
838
+ reason = payload.get("reason")
839
+ except (json.JSONDecodeError, AttributeError):
840
+ reason = None
841
+ branch = f" (when='{when_val}')" if when_val else ""
842
+ branch_reasons.append(f"type '{type_name}'{branch}: {reason or 'does not conform'}")
843
+ continue
844
+ # Exit 4 (unknown type), 5 (environment), 6 (registry contract violation) are
845
+ # ENVIRONMENT-level failures, never value judgments -- E62_S01_T02's own validator
846
+ # contract is explicit that these must not be conflated with non-conforming. A properly
847
+ # loaded chain should never reach an unknown/malformed type here (load-playbooks.sh's own
848
+ # load-time check, E62_S01_T04, already rejects that at confirmation time) -- if it
849
+ # happens anyway, this is a broken environment, not a playbook-author's bad value, so it
850
+ # is NOT routed through the failed/halted board mechanism.
851
+ print(
852
+ f"Error: scripts/validate-typed-object.sh reported an environment-level failure "
853
+ f"(exit {proc.returncode}) verifying step '{step_name}'s declared type '{type_name}': "
854
+ f"{proc.stderr.strip() or proc.stdout.strip()}",
855
+ file=sys.stderr,
856
+ )
857
+ sys.exit(2)
858
+
859
+ if normalized_fallback is not None:
860
+ # No branch conformed as given, but at least one conforms after its own normalize.
861
+ return None, normalized_fallback
862
+
863
+ # No declared branch accepts the value, as given or normalized.
864
+ types_list = ", ".join(types_checked)
865
+ detail = "; ".join(branch_reasons) if branch_reasons else "no declared branch accepts it"
866
+ failure_note = (
867
+ f"step '{step_name}' produced a value that does not conform to its declared "
868
+ f"output_types [{types_list}] (raw value: '{raw_value}'); {detail}"
869
+ )
870
+ return failure_note, None
618
871
 
619
872
  # --- E53_S04_T05: artifact persistence + redaction (persisted copy only -- see header) ---
620
873
  _ABS_PATH_RE = re.compile(r'(/[^\s"\']+)')
@@ -693,6 +946,19 @@ if idx >= len(steps):
693
946
  current_step = steps[idx]
694
947
  state.setdefault("skipped", [])
695
948
 
949
+ # E62_S02_T01: verify a "passed" step's typed-output value against its declared output_types
950
+ # BEFORE the outcome dispatch below, so a non-conforming value can redirect into the EXISTING
951
+ # `outcome == "failed"` branch immediately after -- no second failure mechanism is introduced.
952
+ # Both no-op cases (no declared output_types; no typed-output value at all) leave `outcome` and
953
+ # `extra` untouched, so this is byte-for-byte the pre-existing behavior for them.
954
+ if outcome == "passed" and extra is not None:
955
+ verify_failure_note, resolved_value = verify_typed_output(current_step, extra, pkg_root)
956
+ if verify_failure_note is not None:
957
+ outcome = "failed"
958
+ note = verify_failure_note
959
+ else:
960
+ extra = resolved_value
961
+
696
962
  if outcome == "failed":
697
963
  state["halted"] = True
698
964
  state["failed_step"] = current_step
@@ -757,7 +1023,7 @@ print(json.dumps({
757
1023
  print(f"STATE_FILE: {state_file_path}", file=sys.stderr)
758
1024
  PY
759
1025
 
760
- python3 "$PY_SCRIPT" "$STATE_FILE" "$OUTCOME" "$EXTRA" "$PROJECT_DIR"
1026
+ python3 "$PY_SCRIPT" "$STATE_FILE" "$OUTCOME" "$EXTRA" "$PROJECT_DIR" "$PKG_ROOT"
761
1027
  exit $?
762
1028
 
763
1029
  else
@@ -297,7 +297,7 @@ currently gates on its presence.
297
297
 
298
298
  ### `[SPIKE]` — Bounded Research
299
299
 
300
- **Scope:** story and task level only.
300
+ **Scope:** story and task level, **and rapports** (extended by `E35_S03_T01`, see below).
301
301
 
302
302
  **Meaning:** a time-boxed research or exploration effort whose output is a decision, a design note,
303
303
  or an answered question — **not** shippable implementation code. Existing usage (`E06_S03_spike-editable-board.md`,
@@ -312,6 +312,19 @@ or an answered question — **not** shippable implementation code. Existing usag
312
312
  `[SPIKE]` predates this document; this section formalizes an existing informal convention rather
313
313
  than introducing new behavior.
314
314
 
315
+ **Rapport scope extension (`E35_S03_T01`):** `[SPIKE]` also applies to a problem rapport (see
316
+ `project/rapports/problems/`) via the same title-text convention — a `[SPIKE] <Topic>` prefix on
317
+ the rapport's own title/header field, not a frontmatter key (rapports don't carry board
318
+ frontmatter at all). This marks a rapport as itself surfacing a bounded research question or a
319
+ distinct idea worth tracking, independent of whatever problem the rapport was originally filed
320
+ to report. In practice this happens as part of the idea-lifecycle outcome-record mechanism
321
+ (`E35_S03`): when a `project/ideas.md` entry that originated from a rapport (see
322
+ `skills/idea/SKILL.md`'s "Source-Rapport Link Convention") is later promoted or rejected, the
323
+ outcome-record step updates that rapport with the decision and prefixes its title `[SPIKE]` if
324
+ it isn't already tagged. As with the story/task usage above, this is advisory only — nothing
325
+ validates or enforces the prefix's presence, and a rapport's normal `Type:`/`Related
326
+ Epic/Story/Task` fields (per `templates/PROBLEM_RAPPORT_TEMPLATE.md`) are unaffected by it.
327
+
315
328
  ### `[ARCH]` — Durable Architectural Inventory
316
329
 
317
330
  **Scope:** epic, story, and task level. This is the **first tag extended to epic level** — `[SPIKE]`
@@ -3,8 +3,20 @@ name: <skill-name>
3
3
  description: <One-sentence description of what this skill does and when to use it.>
4
4
  metadata:
5
5
  prefered_agent: <agent-name> # optional — remove if not applicable
6
+ output_types: <type> # optional — forwardable output type(s) this skill produces; a single
7
+ # type string or a list of {when, type} entries. Declare only if this
8
+ # skill genuinely produces output a playbook step could consume
9
+ # (honesty over breadth, E53_S11). Remove if not applicable.
10
+ input_types: <type> # optional — forwarded input type(s) this skill accepts; same two
11
+ # shapes as output_types. May be broadly declared — honesty over
12
+ # breadth is scoped to output_types only (E62_S01_T03).
13
+ # Remove if not applicable.
6
14
  ---
7
15
 
16
+ > Type values come from the canonical vocabulary in `templates/playbook-types.json`. See
17
+ > `docs/skill-authoring.md`'s `output_types` and `input_types` sections for the full contract,
18
+ > including the `text` rule and the normalize-versus-convert boundary.
19
+
8
20
  # <Skill Title> — <Short tagline>
9
21
 
10
22
  ## Instructions
@@ -41,7 +41,7 @@
41
41
  "hooks": [
42
42
  {
43
43
  "type": "command",
44
- "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
44
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\n\"$(git rev-parse --show-toplevel)/scripts/install-worktree-commit-guard.sh\" \"$DIR\" \"$NAME\" 1>&2\necho \"$DIR\"\n"
45
45
  }
46
46
  ]
47
47
  }
@@ -34,7 +34,7 @@
34
34
  "hooks": [
35
35
  {
36
36
  "type": "command",
37
- "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
37
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\n\"$(git rev-parse --show-toplevel)/scripts/install-worktree-commit-guard.sh\" \"$DIR\" \"$NAME\" 1>&2\necho \"$DIR\"\n"
38
38
  }
39
39
  ]
40
40
  }
@@ -32,7 +32,7 @@
32
32
  "hooks": [
33
33
  {
34
34
  "type": "command",
35
- "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
35
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\n\"$(git rev-parse --show-toplevel)/scripts/install-worktree-commit-guard.sh\" \"$DIR\" \"$NAME\" 1>&2\necho \"$DIR\"\n"
36
36
  }
37
37
  ]
38
38
  }
@@ -6,7 +6,7 @@
6
6
  "autoMode": {
7
7
  "allow": [
8
8
  "$defaults",
9
- "Bash(bash skills/mirror-public/scripts/mirror.sh*)"
9
+ "Bash(bash skills/j-mirror-public/scripts/mirror.sh*)"
10
10
  ]
11
11
  },
12
12
  "permissions": {
@@ -30,7 +30,7 @@
30
30
  "hooks": [
31
31
  {
32
32
  "type": "command",
33
- "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
33
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\n\"$(git rev-parse --show-toplevel)/scripts/install-worktree-commit-guard.sh\" \"$DIR\" \"$NAME\" 1>&2\necho \"$DIR\"\n"
34
34
  }
35
35
  ]
36
36
  }
@@ -6,7 +6,7 @@
6
6
  "autoMode": {
7
7
  "allow": [
8
8
  "$defaults",
9
- "Bash(bash skills/mirror-public/scripts/mirror.sh*)"
9
+ "Bash(bash skills/j-mirror-public/scripts/mirror.sh*)"
10
10
  ]
11
11
  },
12
12
  "permissions": {
@@ -28,7 +28,7 @@
28
28
  "hooks": [
29
29
  {
30
30
  "type": "command",
31
- "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
31
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\n\"$(git rev-parse --show-toplevel)/scripts/install-worktree-commit-guard.sh\" \"$DIR\" \"$NAME\" 1>&2\necho \"$DIR\"\n"
32
32
  }
33
33
  ]
34
34
  }
@@ -1,8 +1,42 @@
1
1
  {
2
- "_comment": "Canonical playbook output-type vocabulary for Epic E53's Playbooks v2 StepObject/output_types contract (E53_S03_T02). Extend this vocabulary by adding entries to the 'types' array below -- NEVER by code changes; skills/jenga/scripts/load-playbooks.sh references this file by name in its own header contract (see that script's 'TYPE REGISTRY' section). Governed by the Scrum Master: registry additions/changes route through the normal board-item process rather than an ungoverned edit to this file -- see docs/skill-authoring.md's 'Playbook Type Registry Governance' section for the full policy.",
3
- "types": [
4
- "text",
5
- "id_list",
6
- "file_list"
7
- ]
2
+ "_comment": "Canonical playbook type registry for Epic E53's Playbooks v2 StepObject/output_types contract (E53_S03_T02), converted from a bare list of type names into a map of type descriptors by E62_S01_T01. -- FORMAT: 'types' maps each type name to a descriptor object {\"verify\": <rule-object|null>, \"normalize\": [<transform-name>, ...]}. Both members are optional in the sense that 'verify' may be null and 'normalize' may be empty; a descriptor never carries any other key. -- VERIFY: null means the type has no checkable shape and runtime verification is a deliberate no-op. Otherwise it is an object whose keys are drawn ONLY from the enumerated 'verify_rules' below. The single rule kind defined today is 'per_line': a regex that every element of the normalized value must match. Regexes here are POSIX ERE, not PCRE -- write [0-9] and [^[:space:]], never \\d or \\S -- because the consuming validator is a shell script (E62_S01_T02). -- NORMALIZE: an ordered list of named transforms applied left to right BEFORE verification. The value is treated as a list of lines; 'split_on_comma' further splits each element on commas, 'trim' strips leading and trailing whitespace from each element, 'drop_empty' removes elements that are empty after trimming. Every entry MUST be a bare identifier matching ^[a-z][a-z0-9_]*$ AND a member of 'normalize_vocabulary' below. That closure is the single most important property of this format: because an entry cannot contain whitespace, a free-text or semantic-extraction instruction (e.g. 'extract the ids the skill meant to return') is STRUCTURALLY UNREPRESENTABLE here, not merely discouraged. LLM-driven auto-conversion and a type-repair skill were both rejected at epic level (E62); a transform name the validator does not implement is a hard error, never a string for anything to interpret. -- THE 'text' RULE: 'text' carries \"verify\": null because prose has no checkable shape. This is NOT a licence to treat 'text' as an 'any' in the load-time compatibility lattice. Unverifiable at runtime: yes. Universal acceptor at load time: no -- a universal acceptor would make every text-declaring skill compatible with everything, disarming exactly what E53_S11's honesty-over-breadth policy protects. -- EXTENDING: adding a TYPE is a DATA-ONLY edit and NEVER a code change -- add a key to 'types' whose verify/normalize are composed from the already-enumerated 'verify_rules' and 'normalize_vocabulary'. Adding a new TRANSFORM or a new RULE KIND is the one thing that does require code, since the validator has to implement it; extend the corresponding enumeration here in that same change. Removing or renaming an existing type is a breaking change: it invalidates every SKILL.md declaring it. skills/jenga/scripts/load-playbooks.sh references this file by name in its own header contract (see that script's 'TYPE REGISTRY' section). -- GOVERNANCE: owned by the Scrum Master. Registry additions or changes route through the normal board-item process rather than an ungoverned edit to this file -- see docs/skill-authoring.md's 'Playbook Type Registry Governance' section for the full policy.",
3
+ "verify_rules": [
4
+ "per_line"
5
+ ],
6
+ "normalize_vocabulary": [
7
+ "split_on_comma",
8
+ "trim",
9
+ "drop_empty"
10
+ ],
11
+ "types": {
12
+ "text": {
13
+ "verify": null,
14
+ "normalize": []
15
+ },
16
+ "id_list": {
17
+ "verify": {
18
+ "per_line": "^E[0-9]+(_S[0-9]+(_T[0-9]+)?)?$"
19
+ },
20
+ "normalize": [
21
+ "split_on_comma",
22
+ "trim",
23
+ "drop_empty"
24
+ ]
25
+ },
26
+ "file_list": {
27
+ "verify": {
28
+ "per_line": "^[^[:space:]]"
29
+ },
30
+ "normalize": [
31
+ "trim",
32
+ "drop_empty"
33
+ ]
34
+ },
35
+ "ranked_list": {
36
+ "verify": {
37
+ "per_line": "^[0-9]+\\. [^[:space:]]+ — .+$"
38
+ },
39
+ "normalize": []
40
+ }
41
+ }
8
42
  }