` block with JUST the inner JSON text, stripping the wrapping script tags. This silently corrupts the registry HTML.", "current_code": " } catch {\n return _\n }", "fix_spec": "Change the catch block to return the full original match. The replace callback signature is (fullMatch, openTag, content, closeTag). The variable `_` was used as a placeholder name but it refers to the first captured group (openTag), not the full match. Fix: rename the callback params to use proper names and return openTag + content + closeTag (i.e., reconstruct the original) or capture the full match. Cleanest fix: `return regHtml.replace(metaPattern, (fullMatch, openTag, content, closeTag) => { try { ... } catch { return fullMatch } })`", "rollback": "git checkout -- lib/publish/core.ts", "acceptance_criteria": [ "If hc-metadata contains invalid JSON, the registry HTML is returned unchanged", "The
Before each sprint: git stash push -m "pre-sprint-S{n}"
After each file edit: npx tsc --noEmit
After each sprint: Run full test suite (57 tests)
Rollback single file: git checkout -- path/to/file.ts
Rollback full sprint: git revert HEAD (if committed) or git stash pop (if stashed)
Registry safety: All mutations auto-save page_versions before changes
# Option 1: Give Claude Code the URL
"Read https://ideas.asapai.net/prd-nowpage-issues-v1 and execute Sprint 1"
# Option 2: Fetch raw and parse
curl -s "https://ideas.asapai.net/prd-nowpage-issues-v1?format=raw" | ...
# Option 3: Ralph Loop (autonomous)
"Start a Ralph loop on Sprint S1 of the PRD at ideas.asapai.net/prd-nowpage-issues-v1.
For each issue: read file, apply fix, type-check. After all fixes: run test suite and commit."
| ID | Severity | File | Issue | Fix |
|---|---|---|---|---|
| C1 | CRITICAL | lib/auth/api-key.ts:61,68 |
Empty domain_ids/permissions = full access | Add logging + warning response. Document intent. Backwards compatible. |
| C2 | CRITICAL | lib/publish/core.ts:384 |
JSON parse catch returns wrong variable → strips script tags | Fix callback: return fullMatch instead of return _ |
| C3 | CRITICAL | update-registry/route.ts:~61 |
Null domain → 500 crash | Add null check: return 404 if domain missing |
| C4 | CRITICAL | update-registry/route.ts:~933 |
URL in regex not escaped → false matches | Add escapeRegex() utility, apply to all URL patterns |
| ID | Severity | File | Issue | Fix |
|---|---|---|---|---|
| H1 | HIGH | core.ts:99-100 | CSS class order breaks stat counts | Match both class orderings in regex |
| H2 | HIGH | core.ts:490-505 | Concurrent publish skips version | Add retry on version number conflict |
| H3 | HIGH | publish/route.ts:49 | .single() throws on not-found | Use .maybeSingle() + null check |
| H4 | HIGH | hc-publish.js:~446 | Tag retry loop — no max | Add MAX_RETRIES=3 guard |
| H5 | HIGH | update-registry:732-748 | Day streak breaks at year boundary | Use Date math instead of day-of-year |
| H6 | HIGH | publish/route.ts:51 | Domain check short-circuits | Resolved by H3 |
| ID | Severity | File | Issue | Fix |
|---|---|---|---|---|
| M1 | MED | core.ts:~230 | Dashboard: blockers OR actions, not both | Inject both independently |
| M2 | MED | core.ts:353-376 | Metadata dedup missing | Deduplicate by (text + url) in aggregation |
| M3 | MED | core.ts:564-587 | Registry type fallback mismatch | Skip registration if no matching type found |
| M4 | MED | hc-publish.js:~43 | .env parser breaks on = and quotes | Handle export prefix, strip quotes |
| M5 | MED | webhooks/fire.ts:58 | No SSRF protection | Block localhost/private IPs, require HTTPS |
| M6 | MED | publish/route.ts:64 | Empty tags from comma split | Add .filter(Boolean) |
| M7 | MED | api-key.ts:41 | null expires_at fragile comparison | Add explicit comment + null guard |
| ID | Severity | File | Issue | Fix |
|---|---|---|---|---|
| L1 | LOW | skill-injector.ts | No registry fetch caching | Add sessionStorage cache (5min) |
| L2 | LOW | update-registry/route.ts | No meta-meta recursion guard | Add visited set to cascade |
| L3 | LOW | serve/route.ts | JSX false-positive detection | Use hc-metadata render_mode flag |
| L4 | LOW | pages/route.ts | No slug uniqueness per folder | Query before insert, return 409 |
| L5 | LOW | webhooks/route.ts | No webhook retry | Add webhook_deliveries table + retry cron |
| L6 | LOW | All publish paths | page_versions unbounded | Cleanup old versions (keep 20, >60 days) |
| L7 | LOW | keys/route.ts | No rate limit on key creation | Max 20 active keys per user |
Sprint S1 (Critical):
[ ] Commit snapshot: git add -A && git stash push -m "pre-S1"
[ ] C1: lib/auth/api-key.ts — add logging + scope_warning
[ ] C2: lib/publish/core.ts:384 — fix catch return to fullMatch
[ ] C3: update-registry/route.ts:~61 — add null domain check
[ ] C4: update-registry/route.ts — add escapeRegex utility
[ ] Type check: npx tsc --noEmit
[ ] Test: node scripts/test-publish-api.js (31/31)
[ ] Commit: "Fix critical issues C1-C4: auth logging, JSON catch, null guard, regex escape"
Sprint S2 (High):
[ ] H1: core.ts:99-100 — match both class orderings
[ ] H2: core.ts:490-505 — version insert retry on conflict
[ ] H3: publish/route.ts:49 — .maybeSingle() + null check
[ ] H4: hc-publish.js:~446 — MAX_RETRIES=3
[ ] H5: update-registry:732-748 — Date math for streak
[ ] H6: resolved by H3
[ ] Type check + test + commit
Sprint S3 (Medium):
[ ] M1-M7: apply all fixes
[ ] Type check + test + commit
Sprint S4 (Low):
[ ] L1-L7: apply all fixes
[ ] Type check + test + commit
Post-Execution:
[ ] Publish updated issue tracker with status
[ ] Run cleanup: node scripts/cleanup-test-pages.js --delete
[ ] Push to remote: git push origin main
| Page | URL |
|---|---|
| Issue Tracker (live status) | ideas.asapai.net/nowpage-issue-tracker-2026-02-20 |
| API Manual | ideas.asapai.net/nowpage-api-manual |
| Neural Registry | ideas.asapai.net |
| Meta Registry | ideas.asapai.net/meta-registry |