Skip to content

Commit 3a1397a

Browse files
author
Nestor Martinez
committed
fix(seeds): YAML escape decoding + domain mapping + PYTHON_CMD
Addresses 3 bugs flagged by Codex CLI during PR Luispitik#9 review. All 3 are pre-existing in the feat(seeds) commit of PR Luispitik#8. P1: install.sh hardcoded `python` instead of `$PYTHON_CMD` detected in Step 1. On systems with only python3 in PATH, seed import failed and was silently swallowed by `|| true`. Uses `$PYTHON_CMD` now with a warning fallback when Python is unavailable. P2: parse_yaml_simple stripped quotes but did not decode backslash escapes. Seeds with `"spec\.ts"` were stored with literal backslashes, producing invalid regex. Added _decode_yaml_double_quoted (handles \ \" \n \t \r) and _decode_yaml_single_quoted (`''` → `'`). Fixes 3 broken seeds: e2e-playwright-selectors, security-headers-vercel, supabase-rls-auth-uid. P2: _instinct-activator.sh pre-filters by ALWAYS_DOMAINS + stack-detected domains when context.md exists. Seed domains workflow-general, testing, web-development, saas-development are outside both sets so those seeds never reached regex evaluation. Added DOMAIN_MAP in importer: workflow-general → operations, testing → quality, web-development → frontend, saas-development → stripe. original_domain preserved for traceability. Tests: 3 new in test-seeds.sh (9-11). Suite 8/8 → 11/11 GREEN. Credit: bugs identified by Codex CLI (cross-model review).
1 parent 0befe82 commit 3a1397a

4 files changed

Lines changed: 145 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## Unreleased — fixes to seed importer (addressing Codex review of PR #8)
4+
5+
### Fixed
6+
- **`install.sh:186` — seed import used hardcoded `python` binary**: On Linux/macOS systems where only `python3` is in PATH (the common case — the installer's Step 1 explicitly accepts it), the seed import command failed and was swallowed by `|| true`. The installer reported success but **no seeds were imported into `_instincts-index.json`**. Switched to the `$PYTHON_CMD` detected in Step 1, with a fallback warning when Python is unavailable.
7+
- **`_seed-import.py:85` — YAML escape sequences not decoded**: The minimal YAML parser stripped surrounding quotes but did not interpret backslash escapes. Seeds with trigger patterns like `"spec\\.ts"` or `"vercel\\.json"` were stored with the literal backslashes, producing invalid regex (`spec\\.ts` matches a literal `\` followed by `.ts`, not `spec.ts`). Added `_decode_yaml_double_quoted` (handles `\\ \" \n \t \r`) and `_decode_yaml_single_quoted` (`''``'`). **Fixes 3 broken seeds in the shipped set**: `e2e-playwright-selectors`, `security-headers-vercel`, `supabase-rls-auth-uid`.
8+
- **`_seed-import.py:120` — seed domains filtered out by activator**: `_instinct-activator.sh` pre-filters by `ALWAYS_DOMAINS` (`_default`, `general`, `git`, `security`, `operations`, `quality`) plus stack-detected domains when a project has `context.md`. Seed domains `workflow-general`, `testing`, `web-development`, `saas-development` are not in either set, so `conventional-commits`, `e2e-playwright-selectors`, `nextjs-suspense-boundary`, `stripe-webhook-verify` never reached regex evaluation in normal project sessions. Added `DOMAIN_MAP` in the importer: `workflow-general → operations`, `testing → quality`, `web-development → frontend`, `saas-development → stripe`. Original domain preserved in new `original_domain` field for traceability.
9+
10+
### Tests
11+
- 3 new tests in `tests/test-seeds.sh` (Tests 9-11): YAML escape decoding verified against `e2e-playwright-selectors`, domain mapping verified against `testing → quality`, install.sh usage of `$PYTHON_CMD` verified by grep.
12+
13+
### Credit
14+
Bugs identified by Codex CLI (`/codex:review --base main`) during PR #9 (Laws tier) review. All 3 pre-existing in PR #8 commits.
15+
16+
---
17+
318
## v4.4.1 (2026-04-17)
419

520
### Fixed

core/_seed-import.py

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
3333
Attribution: seed format originated in fs-cortex (MIT © Fernando Montero).
3434
Sinapsis extends it with discrete levels via confidence thresholds.
35+
36+
Domain mapping: `_instinct-activator.sh` pre-filters by domain when the project
37+
has `context.md`. Only `ALWAYS_DOMAINS` ({_default, general, git, security,
38+
operations, quality}) + stack-detected domains are evaluated. Seed domains
39+
outside that set (workflow-general, testing, web-development, saas-development)
40+
are translated to activator-recognized domains at import time so they match
41+
in the expected contexts.
3542
"""
3643
from __future__ import annotations
3744
import argparse
@@ -56,10 +63,53 @@ def default_seeds_dir() -> Path:
5663
return default_skills_dir() / '_seeds' / 'instincts'
5764

5865

66+
def _decode_yaml_double_quoted(s: str) -> str:
67+
"""Decode escape sequences in a YAML double-quoted scalar.
68+
69+
Handles: \\\\\\, \\" → ", \\n → newline, \\t → tab, \\r → CR.
70+
Unknown escapes pass through unchanged (safe for user-authored regex).
71+
"""
72+
out = []
73+
i = 0
74+
n = len(s)
75+
while i < n:
76+
c = s[i]
77+
if c == '\\' and i + 1 < n:
78+
nxt = s[i + 1]
79+
if nxt == '\\':
80+
out.append('\\')
81+
i += 2
82+
elif nxt == '"':
83+
out.append('"')
84+
i += 2
85+
elif nxt == 'n':
86+
out.append('\n')
87+
i += 2
88+
elif nxt == 't':
89+
out.append('\t')
90+
i += 2
91+
elif nxt == 'r':
92+
out.append('\r')
93+
i += 2
94+
else:
95+
out.append(c)
96+
i += 1
97+
else:
98+
out.append(c)
99+
i += 1
100+
return ''.join(out)
101+
102+
103+
def _decode_yaml_single_quoted(s: str) -> str:
104+
"""Decode YAML single-quoted scalar: '' → '"""
105+
return s.replace("''", "'")
106+
107+
59108
def parse_yaml_simple(text: str) -> dict:
60109
"""
61110
Minimal YAML parser for seed format (flat scalar key: value).
62-
Handles single-/double-quoted strings. Ignores nested lists (tags, evidence).
111+
Handles single-/double-quoted strings with escape decoding. Ignores nested
112+
lists (tags, evidence).
63113
"""
64114
result = {}
65115
in_frontmatter = False
@@ -83,13 +133,30 @@ def parse_yaml_simple(text: str) -> dict:
83133
key = key.strip()
84134
value = value.strip()
85135
if value.startswith('"') and value.endswith('"'):
86-
value = value[1:-1]
136+
value = _decode_yaml_double_quoted(value[1:-1])
87137
elif value.startswith("'") and value.endswith("'"):
88-
value = value[1:-1]
138+
value = _decode_yaml_single_quoted(value[1:-1])
89139
result[key] = value
90140
return result
91141

92142

143+
# Domain translation table: seed domain → activator-recognized domain.
144+
# Only applied when seed.domain is in this map. Other domains pass through.
145+
# Rationale: _instinct-activator.sh pre-filters by domain when project context.md
146+
# exists. Unknown domains get silently skipped before regex evaluation. This
147+
# table ensures seed instincts are evaluated in the expected contexts.
148+
DOMAIN_MAP = {
149+
'workflow-general': 'operations',
150+
'testing': 'quality',
151+
'web-development': 'frontend',
152+
'saas-development': 'stripe',
153+
}
154+
155+
156+
def map_domain(domain: str) -> str:
157+
return DOMAIN_MAP.get(domain, domain)
158+
159+
93160
def confidence_to_level(conf_str: str) -> str:
94161
try:
95162
c = float(conf_str)
@@ -115,9 +182,11 @@ def iso_from_date(d: str) -> str:
115182
def map_seed_to_instinct(seed: dict) -> dict:
116183
source = seed.get('source', 'seed')
117184
origin = source if source.startswith('seed') else f'seed:{source}'
118-
return {
185+
raw_domain = seed.get('domain', 'general')
186+
mapped_domain = map_domain(raw_domain)
187+
instinct = {
119188
'id': seed['id'],
120-
'domain': seed.get('domain', 'general'),
189+
'domain': mapped_domain,
121190
'level': confidence_to_level(seed.get('confidence', '0.5')),
122191
'trigger_pattern': seed.get('trigger', ''),
123192
'inject': seed.get('action', ''),
@@ -128,6 +197,9 @@ def map_seed_to_instinct(seed: dict) -> dict:
128197
'first_triggered': iso_from_date(seed.get('first_seen', '')),
129198
'sessions_seen': [],
130199
}
200+
if mapped_domain != raw_domain:
201+
instinct['original_domain'] = raw_domain
202+
return instinct
131203

132204

133205
def parse_id_list(value: str) -> set[str]:

install.sh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,13 @@ chmod +x "$SKILLS_DIR/_seed-import.py" 2>/dev/null || true
183183
if [ -d "$SCRIPT_DIR/seeds/instincts" ]; then
184184
mkdir -p "$SKILLS_DIR/_seeds/instincts"
185185
cp "$SCRIPT_DIR/seeds/instincts"/*.yaml "$SKILLS_DIR/_seeds/instincts/" 2>/dev/null || true
186-
python "$SKILLS_DIR/_seed-import.py" \
187-
--seeds-dir "$SKILLS_DIR/_seeds/instincts" \
188-
--index-path "$SKILLS_DIR/_instincts-index.json" 2>&1 | sed 's/^/ /' || true
186+
if [ -n "$PYTHON_CMD" ]; then
187+
"$PYTHON_CMD" "$SKILLS_DIR/_seed-import.py" \
188+
--seeds-dir "$SKILLS_DIR/_seeds/instincts" \
189+
--index-path "$SKILLS_DIR/_instincts-index.json" 2>&1 | sed 's/^/ /' || true
190+
else
191+
echo -e "${YELLOW} ! Python 3 not available — skipping seed import${NC}"
192+
fi
189193
fi
190194

191195
echo -e "${GREEN} OK${NC} 5 hook scripts + dream cycle + dashboard generator + seed importer installed"

tests/test-seeds.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,52 @@ if [ "$LVL_FD" = "draft" ]; then pass "supabase-rls-auth-uid forced to draft"; e
102102

103103
teardown_sandbox
104104

105+
# ─ Test 9: YAML escape decoding (Codex finding) ─
106+
# "spec\\.ts" in seed YAML must land in JSON as "spec\.ts" (regex-valid),
107+
# NOT "spec\\\\.ts" (literal backslash in regex — never matches).
108+
echo "Test 9: YAML escape decoding in trigger_pattern"
109+
setup_sandbox
110+
python "$IMPORTER" --seeds-dir "$SEEDS_NATIVE" --index-path "$INDEX_PATH" > /dev/null 2>&1
111+
# e2e-playwright-selectors has trigger "playwright|e2e|spec\\.ts|..."
112+
TRIG=$(python -c "import json; d=json.load(open(r'$INDEX_PATH')); m={i['id']:i.get('trigger_pattern','') for i in d.get('instincts',[])}; print(m.get('e2e-playwright-selectors',''))")
113+
# Correct decoded form should contain 'spec\.ts' (2 chars: backslash + dot literal)
114+
# Broken form contains 'spec\\.ts' (3 chars: two backslashes + dot)
115+
if echo "$TRIG" | grep -q 'spec\\\.ts' && ! echo "$TRIG" | grep -q 'spec\\\\\.ts'; then
116+
pass "escape decoded: 'spec\\.ts' in trigger"
117+
else
118+
fail "escape not decoded correctly: got '$TRIG'"
119+
fi
120+
teardown_sandbox
121+
122+
# ─ Test 10: Domain mapping (Codex finding) ─
123+
# seed domain 'testing' must be translated to 'quality' (activator-recognized).
124+
# Also verify original_domain is preserved for traceability.
125+
echo "Test 10: Domain mapping (testing → quality)"
126+
setup_sandbox
127+
python "$IMPORTER" --seeds-dir "$SEEDS_NATIVE" --index-path "$INDEX_PATH" > /dev/null 2>&1
128+
MAPPED=$(python -c "import json; d=json.load(open(r'$INDEX_PATH')); m={i['id']:i.get('domain','') for i in d.get('instincts',[])}; print(m.get('e2e-playwright-selectors',''))")
129+
ORIG=$(python -c "import json; d=json.load(open(r'$INDEX_PATH')); m={i['id']:i.get('original_domain','') for i in d.get('instincts',[])}; print(m.get('e2e-playwright-selectors',''))")
130+
if [ "$MAPPED" = "quality" ] && [ "$ORIG" = "testing" ]; then
131+
pass "testing → quality with original_domain preserved"
132+
else
133+
fail "domain mapping broken: mapped=$MAPPED, original=$ORIG"
134+
fi
135+
teardown_sandbox
136+
137+
# ─ Test 11: install.sh honors $PYTHON_CMD (Codex finding P1) ─
138+
# Ensure install.sh does NOT hardcode 'python' — it must reference $PYTHON_CMD
139+
# detected in Step 1. On systems with only python3 in PATH, hardcoded 'python'
140+
# would fail silently and no seeds would import.
141+
echo "Test 11: install.sh uses \$PYTHON_CMD for seed import"
142+
INSTALL_SH="$SCRIPT_DIR/install.sh"
143+
if grep -q '"\$PYTHON_CMD" "\$SKILLS_DIR/_seed-import.py"' "$INSTALL_SH"; then
144+
pass "install.sh uses \$PYTHON_CMD"
145+
else
146+
fail "install.sh does not reference \$PYTHON_CMD for seed import"
147+
fi
148+
149+
TOTAL=11
150+
105151
echo ""
106152
echo "────────────────────────────"
107153
echo "Results: $PASS/$TOTAL passed, $FAIL failed"

0 commit comments

Comments
 (0)