@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://jenga.local/schemas/jenga-playbook.schema.json",
|
|
4
4
|
"title": "Jenga Multi-Skill Playbook",
|
|
5
|
-
"description": "Schema for a single multi-skill playbook definition consumed by skills/jenga/scripts/load-playbooks.sh (E53_S02_T01). A playbook is a dedicated, versionable data file describing an ORDERED chain of skills that /jenga's natural-language branch may propose (as an editable, confirmable numbered list -- see skills/jenga/scripts/render-playbook-confirmation.sh, E53_S02_T03) when free-text intent spans more than one skill and does not cleanly resolve to a single one via skills/
|
|
5
|
+
"description": "Schema for a single multi-skill playbook definition consumed by skills/jenga/scripts/load-playbooks.sh (E53_S02_T01). A playbook is a dedicated, versionable data file describing an ORDERED chain of skills that /jenga's natural-language branch may propose (as an editable, confirmable numbered list -- see skills/jenga/scripts/render-playbook-confirmation.sh, E53_S02_T03) when free-text intent spans more than one skill and does not cleanly resolve to a single one via skills/jenga/SKILL.md's inlined Skill Matching & Invocation Contract. This file itself (schema.json) is never treated as a playbook -- load-playbooks.sh explicitly excludes it by filename when scanning skills/jenga/playbooks/*.json.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["id", "name", "description", "keywords", "examples", "steps"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -18,25 +18,92 @@
|
|
|
18
18
|
},
|
|
19
19
|
"description": {
|
|
20
20
|
"type": "string",
|
|
21
|
-
"description": "One-sentence explanation of what this playbook accomplishes end-to-end, used as the lowest-priority match signal (description match, same as skills/
|
|
21
|
+
"description": "One-sentence explanation of what this playbook accomplishes end-to-end, used as the lowest-priority match signal (description match, same as skills/jenga/SKILL.md's inlined Skill Matching & Invocation Contract Pass 3) when keywords/examples don't produce a confident match."
|
|
22
22
|
},
|
|
23
23
|
"keywords": {
|
|
24
24
|
"type": "array",
|
|
25
|
-
"description": "Short phrases (1-3 words) for verbatim, case-insensitive keyword matching against the raw natural-language prompt -- the highest-priority match signal (Pass 1), mirroring skills/
|
|
25
|
+
"description": "Short phrases (1-3 words) for verbatim, case-insensitive keyword matching against the raw natural-language prompt -- the highest-priority match signal (Pass 1), mirroring skills/jenga/SKILL.md's inlined Skill Matching & Invocation Contract Pass 1 semantics exactly, but scoped to this playbook's catalog rather than the single-skill catalog.",
|
|
26
26
|
"items": { "type": "string" },
|
|
27
27
|
"minItems": 1
|
|
28
28
|
},
|
|
29
29
|
"examples": {
|
|
30
30
|
"type": "array",
|
|
31
|
-
"description": "Natural-language example prompts a user might type that should resolve to this playbook. Used for the semantic similarity match (Pass 2), mirroring skills/
|
|
31
|
+
"description": "Natural-language example prompts a user might type that should resolve to this playbook. Used for the semantic similarity match (Pass 2), mirroring skills/jenga/SKILL.md's inlined Skill Matching & Invocation Contract Pass 2 semantics. At least one example must plausibly span the full breadth of this playbook's steps (not just its first step) so it is distinguishable from a plain single-skill match.",
|
|
32
32
|
"items": { "type": "string" },
|
|
33
33
|
"minItems": 1
|
|
34
34
|
},
|
|
35
35
|
"steps": {
|
|
36
36
|
"type": "array",
|
|
37
|
-
"description": "Ordered list of canonical skill DIRECTORY
|
|
38
|
-
"items": {
|
|
37
|
+
"description": "Ordered list of steps making up this playbook's chain, in the exact execution order. A step is either a BARE STRING or a STEPOBJECT (E53_S03; composition added by E53_S05). A bare string is shorthand for {\"skill\": \"<name>\"} -- \"bare\" describes the JSON shape, never a bare skill NAME. A skill name is always a canonical skill DIRECTORY name (the directory under skills/<dir>/SKILL.md, e.g. \"j-brainstorm\", not the \"j.brainstorm\" frontmatter/invocation form and not the retired bare \"brainstorm\"); per E50_S10's canonical naming contract the canonical directory is skills/j-<name>/, so a step value carries the j- prefix, while the three permanent exceptions (jenga, jenga-permission-level, index) keep their bare directory names and are written bare. Each skill name MUST resolve to an existing skills/<dir>/SKILL.md at load time -- load-playbooks.sh skips (with a stderr warning) any playbook referencing a nonexistent skill rather than silently including a broken chain in the catalog. NOTE: this schema documents SHAPE only. The cross-step rules -- forward_from naming an EARLIER step whose skill declares a non-empty output_types, conditional.depends_on naming an EARLIER step, composition cycle/depth limits -- are not expressible here and are enforced by load-playbooks.sh, which remains the single source of truth for validation.",
|
|
38
|
+
"items": {
|
|
39
|
+
"oneOf": [
|
|
40
|
+
{
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Bare-string step: shorthand for {\"skill\": \"<name>\"}, unchanged original behavior. An all-bare-string playbook loads byte-for-byte the same as it did before E53_S03, and a bare-string step is never rewritten into object form in the loader's output."
|
|
43
|
+
},
|
|
44
|
+
{ "$ref": "#/definitions/stepObject" }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
39
47
|
"minItems": 2
|
|
40
48
|
}
|
|
49
|
+
},
|
|
50
|
+
"definitions": {
|
|
51
|
+
"stepObject": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "A StepObject step (E53_S03_T01). Carries EXACTLY ONE of the mutually exclusive target fields `skill` or `playbook` -- a step naming both, or naming neither, is rejected by load-playbooks.sh.",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"oneOf": [
|
|
56
|
+
{ "required": ["skill"], "not": { "required": ["playbook"] } },
|
|
57
|
+
{ "required": ["playbook"], "not": { "required": ["skill"] } }
|
|
58
|
+
],
|
|
59
|
+
"properties": {
|
|
60
|
+
"skill": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Invokes a single skill, same as a bare string. Value is a canonical skill directory name (see the `steps` description above)."
|
|
63
|
+
},
|
|
64
|
+
"playbook": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "Composes another playbook by ID (E53_S05). Resolved and flattened in place at load time, so the emitted catalog's `steps` array never contains a raw playbook-type entry. Existence validation, cycle detection, and a configurable nesting-depth limit (project/configs/playbook-config.json's max_composition_depth, default 3) all apply."
|
|
67
|
+
},
|
|
68
|
+
"instruction": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Static natural-language text appended to this step's own invocation message."
|
|
71
|
+
},
|
|
72
|
+
"forward_from": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Names an EARLIER step in the same playbook (by that step's skill name) whose typed output becomes this step's actual invocation input. The named source's SKILL.md must declare a non-empty output_types -- a skill declaring nothing can never be a forward source. Transparent across composition boundaries, since composition resolution runs before this validation."
|
|
75
|
+
},
|
|
76
|
+
"resolve": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Natural-language instructions for reshaping/filtering/type-bridging the value forwarded into this step (e.g. \"pick the first three items\"). A step carrying both `resolve` and `playbook` is rejected at load time: resolve may never pre-authorize a downstream confirmation gate (E53_S07's safety review, permanently rejected)."
|
|
79
|
+
},
|
|
80
|
+
"conditional": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"description": "Runs this step only when the predicate holds against a named earlier step's captured output; otherwise the step is marked `skipped` and the chain continues without halting (E53_S04_T02). Unlike forward_from, this does NOT require the depended-on step to declare output_types.",
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"required": ["depends_on", "predicate"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"depends_on": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "Names an EARLIER skill step in this playbook whose captured output the predicate is evaluated against.",
|
|
89
|
+
"minLength": 1
|
|
90
|
+
},
|
|
91
|
+
"predicate": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "The recognized predicate grammar, whose single source of truth is skills/jenga/scripts/run-playbook-step.sh: non_empty | empty | equals:<value> | not_equals:<value>.",
|
|
94
|
+
"pattern": "^(non_empty|empty|equals:.+|not_equals:.+)$"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"version": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "RESERVED, currently a no-op. Accepted and passed through unchanged; not acted upon by anything yet. Exists so a future schema revision has a place to declare itself without a retroactive migration of every existing playbook."
|
|
101
|
+
},
|
|
102
|
+
"schema_version": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Alternate key name for `version`. Same reserved no-op semantics."
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
41
108
|
}
|
|
42
109
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "understand-then-commit",
|
|
3
|
+
"name": "Understand Then Build",
|
|
4
|
+
"description": "Investigates existing or unfamiliar code first, then runs the full idea-to-commit pipeline on top of what was learned -- a composed chain built from the canonical idea-to-committed playbook, terminating at the commit.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"understand then build",
|
|
7
|
+
"investigate then build",
|
|
8
|
+
"explore then implement",
|
|
9
|
+
"onboard and build",
|
|
10
|
+
"learn the codebase first"
|
|
11
|
+
],
|
|
12
|
+
"examples": [
|
|
13
|
+
"help me understand this codebase first, then plan a change here, build it, and commit it",
|
|
14
|
+
"investigate this existing code, then take an idea through planning, implementation, and a commit",
|
|
15
|
+
"look into what's already here, then brainstorm a fix, build it, and commit the work",
|
|
16
|
+
"onboard me to this project and then walk a new idea from planning through to a committed change"
|
|
17
|
+
],
|
|
18
|
+
"steps": ["j-uncharted", {"playbook": "idea-to-committed"}]
|
|
19
|
+
}
|
|
@@ -68,8 +68,11 @@ import { join } from "path";
|
|
|
68
68
|
import { pathToFileURL } from "url";
|
|
69
69
|
|
|
70
70
|
// The three permanent exceptions to the `j-<name>` canonical directory convention — see
|
|
71
|
-
// docs/skill-authoring.md's Canonical Naming Contract and scripts/
|
|
72
|
-
//
|
|
71
|
+
// docs/skill-authoring.md's Canonical Naming Contract, and scripts/repoint-skill-refs.sh's
|
|
72
|
+
// REPOINT_SKILL_REFS_EXCEPTIONS list, which this mirrors. (It previously mirrored the
|
|
73
|
+
// twin-divergence audit script's NEVER_TWINNED tuple; that script was deleted when
|
|
74
|
+
// E42_S07 retired the twin-parity gate, so both this comment and the cross-check in
|
|
75
|
+
// tests/load-nl-catalog-twin-resolution.bats were repointed at the surviving list.)
|
|
73
76
|
const NEVER_TWINNED = new Set(["jenga", "jenga-permission-level", "index"]);
|
|
74
77
|
|
|
75
78
|
function canonicalSkillDir(name) {
|
|
@@ -46,7 +46,7 @@ fi
|
|
|
46
46
|
|
|
47
47
|
# Resolve the jenga-agent PACKAGE root (where lib/generate-skill-allow-list.js and the canonical
|
|
48
48
|
# skills/ tree actually live) — same monorepo-checkout vs. installed-npm-package detection used
|
|
49
|
-
# by skills/init/scripts/init.sh's PKG_ROOT resolution.
|
|
49
|
+
# by skills/j-init/scripts/init.sh's PKG_ROOT resolution.
|
|
50
50
|
if [ -d "$SCRIPT_DIR/../../../templates" ]; then
|
|
51
51
|
PKG_ROOT="$SCRIPT_DIR/../../.."
|
|
52
52
|
elif [ -d "$JENGA_PROJECT_DIR/node_modules/@jenga-ai/agent/templates" ]; then
|
|
@@ -130,6 +130,84 @@
|
|
|
130
130
|
# anywhere in this loop. `conditional.depends_on` works identically, by the same mechanism.
|
|
131
131
|
#
|
|
132
132
|
# ---------------------------------------------------------------------------
|
|
133
|
+
# TYPE REGISTRY (E62_S01_T04)
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# The canonical type vocabulary this script validates against is `templates/playbook-types.json`
|
|
136
|
+
# (E53_S03_T02, converted into a map of type descriptors by E62_S01_T01), resolved as
|
|
137
|
+
# `<PKG_ROOT>/templates/playbook-types.json` — the SAME `PKG_ROOT` that already resolves
|
|
138
|
+
# `PLAYBOOKS_DIR`/`SKILLS_DIR` above, so the `JENGA_PLAYBOOKS_TEST_ROOT` override (see "TESTING
|
|
139
|
+
# OVERRIDE") redirects it too, and a fixture tree may supply its own vocabulary. The vocabulary is
|
|
140
|
+
# ALWAYS read from that file's `types` map — this script never hardcodes a type name, and adding a
|
|
141
|
+
# type to the registry is a data-only edit that requires no change here (see that file's own
|
|
142
|
+
# `_comment`, "EXTENDING").
|
|
143
|
+
#
|
|
144
|
+
# Two load-time checks are driven from it. Both are about DECLARATIONS ONLY — what a SKILL.md
|
|
145
|
+
# claims — never about an actual runtime VALUE. Verifying a value against its declared type is a
|
|
146
|
+
# separate concern owned by `scripts/validate-typed-object.sh` (E62_S01_T02) and consumed at
|
|
147
|
+
# runtime by `run-playbook-step.sh` (E62_S02); this script never calls it and never sees a value.
|
|
148
|
+
#
|
|
149
|
+
# CHECK 1 — REGISTRY VOCABULARY. For EVERY skill named by a step in the final flattened step
|
|
150
|
+
# list (bare-string steps included, not only StepObjects, and not only `forward_from` sources),
|
|
151
|
+
# every type value the skill declares in its `output_types` or `input_types` frontmatter must be
|
|
152
|
+
# a key in the registry's `types` map. An unrecognized value (e.g. `output_types: banana`) skips
|
|
153
|
+
# the whole playbook with a stderr warning naming the offending value, the field it came from,
|
|
154
|
+
# and — for the `{when, type}` list form — the branch it came from. This check is INDEPENDENT of
|
|
155
|
+
# `forward_from`: it has repo-wide value on a playbook with no forward edges at all.
|
|
156
|
+
#
|
|
157
|
+
# CHECK 2 — OUTPUT/INPUT COMPATIBILITY. Where a step declares `forward_from`, the SOURCE's
|
|
158
|
+
# declared `output_types` must be accepted by the CONSUMER's (i.e. this step's own skill's)
|
|
159
|
+
# declared `input_types`. This runs AFTER the existing declaredness and Blocker-1 structural
|
|
160
|
+
# checks above, so a source that declares nothing, or declares a malformed `{when, type}` entry,
|
|
161
|
+
# is still rejected by its own pre-existing message rather than by this one.
|
|
162
|
+
#
|
|
163
|
+
# THE ALL-BRANCHES RULE — both sides. Either side's declaration may be a single static type string
|
|
164
|
+
# or a list of `{when, type}` branches. This check never needs to know which branch will actually
|
|
165
|
+
# fire on either side, and never executes a classifier script to find out:
|
|
166
|
+
#
|
|
167
|
+
# Compatible IFF every type the SOURCE could produce is accepted under EVERY CONSUMER branch.
|
|
168
|
+
# Equivalently: the set of source types must be a subset of the INTERSECTION of the consumer's
|
|
169
|
+
# accepted types.
|
|
170
|
+
#
|
|
171
|
+
# The source half of that rule (every source branch must produce a type the consumer accepts; any
|
|
172
|
+
# branch that does not rejects the playbook, naming the offending branch) is the rule as originally
|
|
173
|
+
# specified in E53. The CONSUMER half — what a conditional consumer, one whose own `input_types` is
|
|
174
|
+
# a `{when, type}` list, means for compatibility — was left open by E62_S01_T03 and is RATIFIED
|
|
175
|
+
# HERE (2026-09-19, E62_S01_T04) as the conservative dual stated above.
|
|
176
|
+
#
|
|
177
|
+
# Rationale: which consumer branch applies at runtime is no more knowable at load time than which
|
|
178
|
+
# source branch applies. The only answer that is safe regardless of BOTH is the one that holds
|
|
179
|
+
# under both universally. It stays fully deterministic and executes no classifier script. No
|
|
180
|
+
# skill in this repository declares a conditional `input_types` today, so the ruling costs
|
|
181
|
+
# nothing now — it exists to close the ambiguity before it can bite.
|
|
182
|
+
#
|
|
183
|
+
# Mechanically, each consumer branch accepts exactly one type, so the intersection is `{t}` when
|
|
184
|
+
# every branch declares the same `t`, and EMPTY when the branches disagree. An empty intersection
|
|
185
|
+
# rejects every forward into that consumer. That is the intended conservative outcome of the
|
|
186
|
+
# ruling, not an accident of the implementation.
|
|
187
|
+
#
|
|
188
|
+
# BACKWARD COMPATIBILITY — BINDING. A consumer that declares NO `input_types` at all retains
|
|
189
|
+
# today's behavior EXACTLY: the forward is allowed on the existing source-declaredness check alone,
|
|
190
|
+
# and Check 2 short-circuits before any comparison is made. The ABSENCE of a declaration is never a
|
|
191
|
+
# violation. Adding the input side must not, and does not, retroactively break a single existing
|
|
192
|
+
# playbook. (`docs/skill-authoring.md`'s `input_types` section states the same guarantee from the
|
|
193
|
+
# skill author's side.)
|
|
194
|
+
#
|
|
195
|
+
# `text` IS NOT A WILDCARD. `text` carries `"verify": null` in the registry because prose has no
|
|
196
|
+
# checkable shape, but it is a specific type in this compatibility lattice — never an `any`. It is
|
|
197
|
+
# neither an `output_types` that satisfies every `input_types` nor an `input_types` that accepts
|
|
198
|
+
# every `output_types`. Treating it as a universal acceptor would make every text-declaring skill
|
|
199
|
+
# compatible with everything and render this check decorative, which is precisely what E53_S11's
|
|
200
|
+
# honesty-over-breadth policy exists to prevent. Nothing in this script special-cases it.
|
|
201
|
+
#
|
|
202
|
+
# MISSING REGISTRY — DELIBERATE FAIL-OPEN. If the registry file is absent or unparseable, CHECK 1
|
|
203
|
+
# is a silent no-op (no warning, no rejection); Check 2 is unaffected, since comparing declared
|
|
204
|
+
# type NAMES needs no vocabulary. This matches this script's existing convention for an absent
|
|
205
|
+
# optional input (a missing `project/.playbooks/` directory, a missing `playbook-config.json`), and
|
|
206
|
+
# it is unreachable in a real invocation: PKG_ROOT detection is itself keyed on `templates/`
|
|
207
|
+
# existing. It exists so a fixture tree under `JENGA_PLAYBOOKS_TEST_ROOT` may supply its own
|
|
208
|
+
# registry, or deliberately supply none.
|
|
209
|
+
#
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
133
211
|
# CONDITIONAL RESOLUTION (E53_S04_T02)
|
|
134
212
|
# ---------------------------------------------------------------------------
|
|
135
213
|
# A step's `conditional: {"depends_on": "<name>", "predicate": "<predicate>"}` is validated at load
|
|
@@ -418,6 +496,20 @@
|
|
|
418
496
|
# - A `conditional`'s `predicate` does not match the recognized grammar
|
|
419
497
|
# (`non_empty`/`empty`/`equals:<value>`/`not_equals:<value>`, defined
|
|
420
498
|
# in `run-playbook-step.sh`'s own header) (E53_S04_T02) -> skipped
|
|
499
|
+
# - ANY step's skill declares an `output_types`/`input_types` value
|
|
500
|
+
# that is not a key in `templates/playbook-types.json`'s `types`
|
|
501
|
+
# map (Check 1) (E62_S01_T04) -> skipped
|
|
502
|
+
# (applies to every skill-type step, bare string included -- not only
|
|
503
|
+
# `forward_from` sources; a silent no-op if the registry file itself is
|
|
504
|
+
# missing/unparseable, see "TYPE REGISTRY" above)
|
|
505
|
+
# - A `forward_from` source declares an `output_types` the consumer step's
|
|
506
|
+
# own `input_types` does not accept under EVERY consumer branch
|
|
507
|
+
# (Check 2, the all-branches rule) (E62_S01_T04) -> skipped
|
|
508
|
+
# (a consumer declaring NO `input_types` is never a violation -- the
|
|
509
|
+
# forward is allowed on the source-declaredness check alone, exactly as
|
|
510
|
+
# before this task)
|
|
511
|
+
# - A consumer's `input_types` list carries an entry missing `when`/`type`
|
|
512
|
+
# (E62_S01_T04) -> skipped
|
|
421
513
|
#
|
|
422
514
|
# ---------------------------------------------------------------------------
|
|
423
515
|
# EXIT CODES
|
|
@@ -474,7 +566,7 @@ else
|
|
|
474
566
|
|
|
475
567
|
# Resolve the jenga-agent PACKAGE root (where the canonical skills/ tree actually lives) — same
|
|
476
568
|
# monorepo-checkout vs. installed-npm-package detection used by
|
|
477
|
-
# skills/jenga/scripts/load-nl-catalog.sh's PKG_ROOT resolution and skills/init/scripts/init.sh.
|
|
569
|
+
# skills/jenga/scripts/load-nl-catalog.sh's PKG_ROOT resolution and skills/j-init/scripts/init.sh.
|
|
478
570
|
if [ -d "$SCRIPT_DIR/../../../templates" ]; then
|
|
479
571
|
PKG_ROOT="$SCRIPT_DIR/../../.."
|
|
480
572
|
PROJECT_DIR="$JENGA_PROJECT_DIR"
|
|
@@ -515,6 +607,11 @@ project_dir = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None
|
|
|
515
607
|
# --- E53_S06_T02: additive `lookup <id>` CLI mode --------------------------------------------
|
|
516
608
|
mode = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else "catalog"
|
|
517
609
|
lookup_id = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
610
|
+
# --- E62_S01_T04: the jenga-agent PACKAGE root, where `templates/playbook-types.json` lives ---
|
|
611
|
+
# Already resolved by the bash wrapper above (and already honoring JENGA_PLAYBOOKS_TEST_ROOT) --
|
|
612
|
+
# threaded in here rather than re-derived, so there is exactly one PKG_ROOT resolution in this
|
|
613
|
+
# script. See header "TYPE REGISTRY".
|
|
614
|
+
pkg_root = sys.argv[6] if len(sys.argv) > 6 and sys.argv[6] else None
|
|
518
615
|
|
|
519
616
|
# --- E53_S09_T01: project-local playbook source directory ------------------------------------
|
|
520
617
|
# `project/.playbooks/`, resolved relative to the SAME project_dir already threaded above (which
|
|
@@ -568,6 +665,32 @@ def load_max_composition_depth(proj_dir):
|
|
|
568
665
|
|
|
569
666
|
MAX_COMPOSITION_DEPTH = load_max_composition_depth(project_dir)
|
|
570
667
|
|
|
668
|
+
|
|
669
|
+
# --- E62_S01_T04: the canonical type vocabulary -------------------------------------------------
|
|
670
|
+
def load_type_registry(root):
|
|
671
|
+
"""Read the set of known type names from `<root>/templates/playbook-types.json`'s `types` map.
|
|
672
|
+
|
|
673
|
+
Returns None when the registry is missing or unparseable, which makes the vocabulary check a
|
|
674
|
+
deliberate silent no-op -- see header 'TYPE REGISTRY' ("MISSING REGISTRY"). The vocabulary is
|
|
675
|
+
ALWAYS read from that file; no type name is ever hardcoded here, so adding a type to the
|
|
676
|
+
registry is a data-only edit requiring no change to this script.
|
|
677
|
+
"""
|
|
678
|
+
if not root:
|
|
679
|
+
return None
|
|
680
|
+
registry_path = os.path.join(root, "templates", "playbook-types.json")
|
|
681
|
+
try:
|
|
682
|
+
with open(registry_path, encoding="utf-8") as fh:
|
|
683
|
+
registry = json.load(fh)
|
|
684
|
+
except (OSError, ValueError):
|
|
685
|
+
return None
|
|
686
|
+
types = registry.get("types") if isinstance(registry, dict) else None
|
|
687
|
+
if not isinstance(types, dict):
|
|
688
|
+
return None
|
|
689
|
+
return set(types.keys())
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
TYPE_REGISTRY = load_type_registry(pkg_root)
|
|
693
|
+
|
|
571
694
|
catalog = []
|
|
572
695
|
# --- E53_S06_T02: per-basename skip-reason capture -------------------------------------------
|
|
573
696
|
# Threaded through PASS 1, PASS 2 (resolve_playbook), and PASS 3 below -- whenever a playbook
|
|
@@ -662,8 +785,11 @@ def step_skill_name(step):
|
|
|
662
785
|
_FRONTMATTER_RE = re.compile(r'^---\r?\n(.*?)\r?\n---', re.DOTALL)
|
|
663
786
|
|
|
664
787
|
|
|
665
|
-
def
|
|
666
|
-
"""Best-effort extraction of
|
|
788
|
+
def extract_types_field(skill_md_path, field):
|
|
789
|
+
"""Best-effort extraction of a type-declaration frontmatter field from a SKILL.md.
|
|
790
|
+
|
|
791
|
+
`field` is `output_types` or `input_types` (E62_S01_T04) -- both take the SAME two shapes (see
|
|
792
|
+
docs/skill-authoring.md), so they are read by this one parser rather than two copies of it.
|
|
667
793
|
|
|
668
794
|
Returns None if the file/field is missing or unparseable, a `str` for the single-static-type
|
|
669
795
|
form, or a `list[dict]` for the `{when, type}` list form. This is a small, targeted parser for
|
|
@@ -684,7 +810,7 @@ def extract_output_types(skill_md_path):
|
|
|
684
810
|
fm_lines = fm_match.group(1).splitlines()
|
|
685
811
|
|
|
686
812
|
for i, line in enumerate(fm_lines):
|
|
687
|
-
key_match = re.match(r'
|
|
813
|
+
key_match = re.match(r'^%s:\s*(.*)$' % re.escape(field), line)
|
|
688
814
|
if not key_match:
|
|
689
815
|
continue
|
|
690
816
|
|
|
@@ -727,6 +853,104 @@ def extract_output_types(skill_md_path):
|
|
|
727
853
|
return None
|
|
728
854
|
|
|
729
855
|
|
|
856
|
+
def extract_output_types(skill_md_path):
|
|
857
|
+
"""The `output_types` half of extract_types_field -- kept as a named wrapper so every
|
|
858
|
+
pre-existing call site (E53_S03_T03/T04) reads exactly as it did before E62_S01_T04."""
|
|
859
|
+
return extract_types_field(skill_md_path, "output_types")
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def extract_input_types(skill_md_path):
|
|
863
|
+
"""The `input_types` half of extract_types_field (E62_S01_T04)."""
|
|
864
|
+
return extract_types_field(skill_md_path, "input_types")
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# --- E62_S01_T04: shared normalization of either declaration shape ------------------------------
|
|
868
|
+
def declared_type_entries(value):
|
|
869
|
+
"""Normalize an `output_types`/`input_types` value into `[(type_or_None, when_or_None), ...]`.
|
|
870
|
+
|
|
871
|
+
A single static type string yields ONE entry whose `when` is None; a `{when, type}` list yields
|
|
872
|
+
one entry per branch. A malformed list entry (not an object, or missing `type`) yields a
|
|
873
|
+
`(None, when_or_None)` entry rather than being dropped, so a caller can tell "no branches" from
|
|
874
|
+
"a branch this function could not read". Both checks in header 'TYPE REGISTRY' consume this, so
|
|
875
|
+
neither re-derives the two shapes.
|
|
876
|
+
"""
|
|
877
|
+
if not value:
|
|
878
|
+
return []
|
|
879
|
+
if isinstance(value, str):
|
|
880
|
+
return [(value, None)]
|
|
881
|
+
if isinstance(value, list):
|
|
882
|
+
entries = []
|
|
883
|
+
for item in value:
|
|
884
|
+
if isinstance(item, dict):
|
|
885
|
+
entries.append((item.get("type") or None, item.get("when") or None))
|
|
886
|
+
else:
|
|
887
|
+
entries.append((None, None))
|
|
888
|
+
return entries
|
|
889
|
+
return []
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def validate_declared_vocabulary(idx, skill_name, skill_md_path, registry):
|
|
893
|
+
"""CHECK 1 -- every type value this skill declares must be a key in the registry's `types` map.
|
|
894
|
+
|
|
895
|
+
Returns an error string naming the offending value (and its branch, for the list form), or None.
|
|
896
|
+
A None `registry` never reaches here (the caller skips the check entirely -- see header
|
|
897
|
+
'TYPE REGISTRY', "MISSING REGISTRY").
|
|
898
|
+
"""
|
|
899
|
+
known = ", ".join(sorted(registry)) if registry else "<none>"
|
|
900
|
+
for field in ("output_types", "input_types"):
|
|
901
|
+
declared = extract_types_field(skill_md_path, field)
|
|
902
|
+
for type_name, when_val in declared_type_entries(declared):
|
|
903
|
+
if type_name is None:
|
|
904
|
+
# A malformed `{when, type}` entry. Deliberately NOT this check's business: the
|
|
905
|
+
# pre-existing Blocker-1 structural check (E53_S03_T04) already owns that rejection
|
|
906
|
+
# for a forward source, with its own message.
|
|
907
|
+
continue
|
|
908
|
+
if type_name not in registry:
|
|
909
|
+
branch = " (branch when='%s')" % when_val if when_val else ""
|
|
910
|
+
return (
|
|
911
|
+
f"step {idx} skill '{skill_name}' declares {field} '{type_name}'{branch}, "
|
|
912
|
+
f"which is not a type in templates/playbook-types.json "
|
|
913
|
+
f"(known types: {known})"
|
|
914
|
+
)
|
|
915
|
+
return None
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def accepted_input_types(value):
|
|
919
|
+
"""The CONSUMER side of the all-branches rule (E62_S01_T04, ratified 2026-09-19).
|
|
920
|
+
|
|
921
|
+
Returns `(accepted_set_or_None, error_or_None)`:
|
|
922
|
+
|
|
923
|
+
- `(None, None)` -- the consumer declares NO `input_types`. This is the BINDING backward-
|
|
924
|
+
compatible case: the caller must allow the forward on the source-
|
|
925
|
+
declaredness check alone, exactly as before this task. It is distinct
|
|
926
|
+
from `(set(), None)` ("declares branches that agree on nothing"), and
|
|
927
|
+
conflating the two would be precisely the retroactive break the task
|
|
928
|
+
forbids.
|
|
929
|
+
- `(set, None)` -- the set of types accepted under EVERY declared branch, i.e. the
|
|
930
|
+
INTERSECTION. A single static type yields `{t}`. A `{when, type}` list
|
|
931
|
+
yields `{t}` when every branch declares the same `t`, and an EMPTY set
|
|
932
|
+
when they disagree -- an empty set rejects every forward, which is the
|
|
933
|
+
intended conservative outcome of the ruling (see header 'TYPE REGISTRY').
|
|
934
|
+
- `(None, str)` -- a malformed declaration; the string is the reason.
|
|
935
|
+
"""
|
|
936
|
+
if not value:
|
|
937
|
+
return None, None
|
|
938
|
+
if isinstance(value, str):
|
|
939
|
+
return {value}, None
|
|
940
|
+
if not isinstance(value, list):
|
|
941
|
+
return None, "declares a malformed input_types (neither a type string nor a list)"
|
|
942
|
+
|
|
943
|
+
accepted = None
|
|
944
|
+
for item in value:
|
|
945
|
+
if not isinstance(item, dict) or not item.get("when") or not item.get("type"):
|
|
946
|
+
return None, "declares a malformed input_types entry (missing 'when' or 'type')"
|
|
947
|
+
branch_set = {item["type"]}
|
|
948
|
+
accepted = branch_set if accepted is None else (accepted & branch_set)
|
|
949
|
+
if accepted is None:
|
|
950
|
+
return None, "declares an empty input_types list"
|
|
951
|
+
return accepted, None
|
|
952
|
+
|
|
953
|
+
|
|
730
954
|
try:
|
|
731
955
|
builtin_filenames = sorted(
|
|
732
956
|
f for f in os.listdir(playbooks_dir)
|
|
@@ -1014,7 +1238,26 @@ for pid in order:
|
|
|
1014
1238
|
continue
|
|
1015
1239
|
|
|
1016
1240
|
validation_error = None
|
|
1241
|
+
|
|
1242
|
+
# --- E62_S01_T04 CHECK 1: registry vocabulary validation (see header 'TYPE REGISTRY') -------
|
|
1243
|
+
# Its own loop, deliberately separate from the forward_from/conditional loop below: that loop
|
|
1244
|
+
# skips every non-dict step, and this check must cover BARE-STRING steps too -- it is keyed on
|
|
1245
|
+
# the step's resolved skill name, not on the step carrying any particular field. A None
|
|
1246
|
+
# registry (missing/unparseable file) makes the whole check a silent no-op.
|
|
1247
|
+
if TYPE_REGISTRY is not None:
|
|
1248
|
+
for idx, step in enumerate(flattened_steps):
|
|
1249
|
+
step_name = step_skill_name(step)
|
|
1250
|
+
if not step_name:
|
|
1251
|
+
continue
|
|
1252
|
+
validation_error = validate_declared_vocabulary(
|
|
1253
|
+
idx, step_name, os.path.join(skills_dir, step_name, "SKILL.md"), TYPE_REGISTRY
|
|
1254
|
+
)
|
|
1255
|
+
if validation_error:
|
|
1256
|
+
break
|
|
1257
|
+
|
|
1017
1258
|
for idx, step in enumerate(flattened_steps):
|
|
1259
|
+
if validation_error:
|
|
1260
|
+
break
|
|
1018
1261
|
if not isinstance(step, dict):
|
|
1019
1262
|
continue
|
|
1020
1263
|
|
|
@@ -1085,6 +1328,48 @@ for pid in order:
|
|
|
1085
1328
|
if validation_error:
|
|
1086
1329
|
break
|
|
1087
1330
|
|
|
1331
|
+
# --- E62_S01_T04 CHECK 2: output/input compatibility under the all-branches rule -------
|
|
1332
|
+
# Runs AFTER the two checks above on purpose: a source that declares nothing, or declares
|
|
1333
|
+
# a malformed {when, type} entry, is still rejected by its own pre-existing message, never
|
|
1334
|
+
# by this one. See header 'TYPE REGISTRY'.
|
|
1335
|
+
consumer_name = step_skill_name(step)
|
|
1336
|
+
consumer_input_val = (
|
|
1337
|
+
extract_input_types(os.path.join(skills_dir, consumer_name, "SKILL.md"))
|
|
1338
|
+
if consumer_name
|
|
1339
|
+
else None
|
|
1340
|
+
)
|
|
1341
|
+
accepted, accepted_error = accepted_input_types(consumer_input_val)
|
|
1342
|
+
if accepted_error:
|
|
1343
|
+
validation_error = f"step {idx} forward_from consumer '{consumer_name}' {accepted_error}"
|
|
1344
|
+
break
|
|
1345
|
+
if accepted is not None:
|
|
1346
|
+
# `accepted is None` is the BINDING backward-compatible path: a consumer declaring no
|
|
1347
|
+
# `input_types` is allowed on the source-declaredness check alone, exactly as before
|
|
1348
|
+
# this task. Nothing below runs for it.
|
|
1349
|
+
offending = next(
|
|
1350
|
+
(
|
|
1351
|
+
(type_val, when_val)
|
|
1352
|
+
for type_val, when_val in declared_type_entries(output_types_val)
|
|
1353
|
+
if type_val is not None and type_val not in accepted
|
|
1354
|
+
),
|
|
1355
|
+
None,
|
|
1356
|
+
)
|
|
1357
|
+
if offending:
|
|
1358
|
+
offending_type, offending_when = offending
|
|
1359
|
+
branch_desc = (
|
|
1360
|
+
f"output_types branch when='{offending_when}'"
|
|
1361
|
+
if offending_when
|
|
1362
|
+
else "static output_types"
|
|
1363
|
+
)
|
|
1364
|
+
accepted_desc = ", ".join(sorted(accepted)) if accepted else "<none>"
|
|
1365
|
+
validation_error = (
|
|
1366
|
+
f"step {idx} 'forward_from' source '{source_name}' {branch_desc} produces "
|
|
1367
|
+
f"type '{offending_type}', which consumer '{consumer_name}' does not accept "
|
|
1368
|
+
f"under every input_types branch (accepted under all branches: "
|
|
1369
|
+
f"{accepted_desc})"
|
|
1370
|
+
)
|
|
1371
|
+
break
|
|
1372
|
+
|
|
1088
1373
|
if validation_error:
|
|
1089
1374
|
print(f"Warning: {path} {validation_error} — skipped", file=sys.stderr)
|
|
1090
1375
|
skip_reasons[pid] = validation_error
|
|
@@ -1129,5 +1414,5 @@ if mode == "lookup":
|
|
|
1129
1414
|
print(json.dumps(catalog, indent=2))
|
|
1130
1415
|
PY
|
|
1131
1416
|
|
|
1132
|
-
python3 "$PY_SCRIPT" "$PLAYBOOKS_DIR" "$SKILLS_DIR" "$PROJECT_DIR" "$MODE" "$LOOKUP_ID"
|
|
1417
|
+
python3 "$PY_SCRIPT" "$PLAYBOOKS_DIR" "$SKILLS_DIR" "$PROJECT_DIR" "$MODE" "$LOOKUP_ID" "$PKG_ROOT"
|
|
1133
1418
|
exit $?
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
# skills/jenga/scripts/match-playbook.sh
|
|
4
4
|
#
|
|
5
5
|
# Deterministic PLAYBOOK matcher for `/jenga`'s natural-language branch (E53_S02_T02). Runs the
|
|
6
|
-
# same three-pass matching *philosophy* as `skills/
|
|
7
|
-
# example similarity -> description), but scoped to the playbook
|
|
8
|
-
# `load-playbooks.sh` (E53_S02_T01) instead of the single-skill catalog
|
|
9
|
-
# produces for `/route`/`/jenga`'s existing single-skill matching.
|
|
6
|
+
# same three-pass matching *philosophy* as `skills/jenga/SKILL.md`'s inlined Skill Matching &
|
|
7
|
+
# Invocation Contract (keyword -> example similarity -> description), but scoped to the playbook
|
|
8
|
+
# catalog produced by `load-playbooks.sh` (E53_S02_T01) instead of the single-skill catalog
|
|
9
|
+
# `load-nl-catalog.sh` produces for `/route`/`/jenga`'s existing single-skill matching.
|
|
10
10
|
#
|
|
11
11
|
# ---------------------------------------------------------------------------
|
|
12
12
|
# THIS IS A FALLBACK — READ BEFORE WIRING (E53_S02_T04)
|