@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.
- package/README.md +85 -78
- package/agents/developer.md +1 -1
- package/agents/scrum-master.md +20 -2
- package/agents/tester.md +3 -3
- package/hooks/on_session_end.sh +5 -5
- package/lib/generate-agent-context.js +2 -2
- package/lib/generate-copilot-hooks.js +1 -1
- package/lib/generate-skill-allow-list.js +79 -8
- package/lib/mirror.js +1 -1
- package/lib/postinstall-manifest.js +1 -1
- package/lib/skill-allow-list.json +2 -2
- package/mcp/help/index.js +8 -17
- package/mcp/help/scan.js +73 -0
- package/package.json +5 -1
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/api/parsers/knowledge-graph.js +100 -9
- package/project/app/api/routes/health.js +36 -0
- package/project/app/api/scripts/capture-snapshot.js +9 -6
- package/project/app/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
- package/project/app/ui/dist/index.html +2 -2
- package/project/app/ui/package.json +4 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
- package/scripts/acquire-concurrency-slot.sh +35 -5
- package/scripts/apply-j-prefix.sh +46 -5
- package/scripts/build-pages-site.sh +1 -1
- package/scripts/check-public-playbook-steps.sh +158 -52
- package/scripts/check-publicignore-match.sh +2 -2
- package/scripts/compute-deploy-reconcile.sh +5 -5
- package/scripts/delete-bare-skill-dirs.sh +329 -0
- package/scripts/generate-legacy-shipped-paths.js +2 -2
- package/scripts/idea_manager.sh +273 -3
- package/scripts/mark-deployed.sh +2 -2
- package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
- package/scripts/populate-knowledge-graph.js +213 -5
- package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
- package/scripts/postinstall.js +1 -1
- package/scripts/release-concurrency-slot.sh +34 -4
- package/scripts/render-ranked-list.sh +270 -0
- package/scripts/repoint-dead-bare-path-prose.py +81 -0
- package/scripts/repoint-skill-refs.sh +539 -0
- package/scripts/rewrite-stale-skill-preambles.py +188 -0
- package/scripts/strip-polyfill-frontmatter.py +166 -0
- package/scripts/todo_manager.sh +16 -1
- package/scripts/validate-typed-object.sh +750 -0
- package/scripts/verify-legacy-seed-reconcile.sh +10 -10
- package/scripts/verify-postinstall-reconcile.sh +7 -7
- package/scripts/write-context-digest.sh +1 -1
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +5 -6
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-privatized.sh +4 -4
- package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
- package/skills/j-commit/SKILL.md +3 -4
- package/skills/j-continue/SKILL.md +5 -6
- package/skills/j-deep-dive/SKILL.md +3 -4
- package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +100 -18
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc-sync/SKILL.md +4 -4
- package/skills/j-dooo/SKILL.md +6 -15
- package/skills/j-error/SKILL.md +3 -4
- package/skills/j-evaluate/SKILL.md +3 -4
- package/skills/j-examplify/SKILL.md +3 -4
- package/skills/j-gitignore/SKILL.md +157 -0
- package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
- package/skills/j-gitignore/scripts/_catalog.sh +105 -0
- package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
- package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
- package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +80 -9
- package/skills/j-idea/assets/idea_template.md +1 -1
- package/skills/j-improve/SKILL.md +4 -5
- package/skills/j-init/SKILL.md +23 -14
- package/skills/j-init/assets/scope-thresholds_template.json +5 -2
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +9 -7
- package/skills/j-init/scripts/init.sh +4 -4
- package/skills/j-jbp/SKILL.md +3 -4
- package/skills/j-lgtm/SKILL.md +3 -4
- package/skills/j-pi-plan/SKILL.md +3 -4
- package/skills/j-playbook/SKILL.md +1 -1
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +4 -5
- package/skills/j-publish/adapters/npm-ci.md +6 -1
- package/skills/j-publish/adapters/npm.md +1 -1
- package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
- package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +40 -7
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +13 -13
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +4 -5
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +4 -5
- package/skills/j-status/SKILL.md +18 -4
- package/skills/j-todo/SKILL.md +44 -5
- package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
- package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
- package/skills/j-uncharted/SKILL.md +252 -13
- package/skills/j-uncharted/scripts/detect-dependencies.sh +80 -22
- package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/elicitation-state.sh +1 -1
- package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
- package/skills/j-uncharted/scripts/run-engine.sh +36 -2
- package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +4 -5
- package/skills/jenga/SKILL.md +106 -11
- package/skills/jenga/playbooks/board-hygiene.json +32 -0
- package/skills/jenga/playbooks/schema.json +73 -6
- package/skills/jenga/playbooks/understand-then-commit.json +19 -0
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
- package/skills/jenga/scripts/load-playbooks.sh +290 -5
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
- package/templates/SKILL_TEMPLATE.md +12 -0
- package/templates/permission-levels/level-1-locked.json +1 -1
- package/templates/permission-levels/level-2-guarded.json +1 -1
- package/templates/permission-levels/level-3-standard.json +1 -1
- package/templates/permission-levels/level-4-elevated.json +2 -2
- package/templates/permission-levels/level-5-unrestricted.json +2 -2
- package/templates/playbook-types.json +40 -6
- package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +0 -1
- package/scripts/audit-twin-divergence.sh +0 -625
- package/scripts/generate-j-alias.sh +0 -333
- package/skills/j-dev-done/SKILL.md +0 -53
- 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
|
|
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
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
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
|
}
|