@tyroneross/build-loop 0.30.3
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/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
|
|
3
|
+
# Incident Documentation Guide
|
|
4
|
+
|
|
5
|
+
Complete guide to documenting debugging incidents for effective future retrieval.
|
|
6
|
+
|
|
7
|
+
## Incident Structure
|
|
8
|
+
|
|
9
|
+
Each incident contains these fields:
|
|
10
|
+
|
|
11
|
+
### Identification
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"incident_id": "INC_20241225_143052_abc1",
|
|
16
|
+
"timestamp": 1735135852000,
|
|
17
|
+
"session_id": "SESSION_1735135800000_xyz"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **incident_id**: Auto-generated, format `INC_YYYYMMDD_HHMMSS_random`
|
|
22
|
+
- **timestamp**: Unix timestamp of when incident was stored
|
|
23
|
+
- **session_id**: Links to the debugging session that produced this fix
|
|
24
|
+
|
|
25
|
+
### Symptom
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"symptom": "Search results not displaying after typing in the search box",
|
|
30
|
+
"symptom_type": "ui"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **symptom**: User-facing description of what went wrong
|
|
35
|
+
- **symptom_type**: Category (ui, api, logic, crash, performance, build)
|
|
36
|
+
|
|
37
|
+
**Writing effective symptoms:**
|
|
38
|
+
- Describe observable behavior
|
|
39
|
+
- Include context (what action triggered it)
|
|
40
|
+
- Avoid implementation details
|
|
41
|
+
- Keep it searchable
|
|
42
|
+
|
|
43
|
+
### Root Cause
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"root_cause": {
|
|
48
|
+
"description": "The debounce hook was resetting the search query on every keystroke because the dependency array included a reference-unstable callback. Each render created a new callback reference, triggering the debounce reset.",
|
|
49
|
+
"file": "src/hooks/useDebounce.ts",
|
|
50
|
+
"line_range": [15, 28],
|
|
51
|
+
"code_snippet": "const debouncedValue = useMemo(() => {...}, [value, callback])",
|
|
52
|
+
"category": "react-hooks",
|
|
53
|
+
"confidence": 0.95
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **description**: Detailed technical explanation (minimum 50 characters)
|
|
59
|
+
- **file**: Primary file where the bug existed
|
|
60
|
+
- **line_range**: Affected line numbers
|
|
61
|
+
- **code_snippet**: Relevant code (helps future matching)
|
|
62
|
+
- **category**: Technical category (react-hooks, api, config, dependency, logic)
|
|
63
|
+
- **confidence**: How certain the diagnosis is (0.0-1.0)
|
|
64
|
+
|
|
65
|
+
**Root cause quality checklist:**
|
|
66
|
+
- Explains WHY the bug occurred, not just WHERE
|
|
67
|
+
- Includes enough detail to understand without reading the code
|
|
68
|
+
- References specific code patterns or concepts
|
|
69
|
+
- Assigns appropriate confidence based on certainty
|
|
70
|
+
|
|
71
|
+
### Fix
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"fix": {
|
|
76
|
+
"approach": "Wrapped the callback in useCallback to stabilize the reference, then updated the debounce hook's dependency array to only include the memoized callback.",
|
|
77
|
+
"changes": [
|
|
78
|
+
{
|
|
79
|
+
"file": "src/hooks/useDebounce.ts",
|
|
80
|
+
"lines_changed": 8,
|
|
81
|
+
"change_type": "modify",
|
|
82
|
+
"summary": "Added useCallback wrapper and updated dependencies"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"file": "src/components/SearchBar.tsx",
|
|
86
|
+
"lines_changed": 3,
|
|
87
|
+
"change_type": "modify",
|
|
88
|
+
"summary": "Memoized the search handler callback"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"pattern_used": null,
|
|
92
|
+
"time_to_fix": 25
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **approach**: High-level description of the solution
|
|
98
|
+
- **changes**: Array of file changes with details
|
|
99
|
+
- **pattern_used**: Pattern ID if an existing pattern was applied
|
|
100
|
+
- **time_to_fix**: Minutes spent (helps estimate similar bugs)
|
|
101
|
+
|
|
102
|
+
### Verification
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"verification": {
|
|
107
|
+
"status": "verified",
|
|
108
|
+
"regression_tests_passed": true,
|
|
109
|
+
"user_journey_tested": true,
|
|
110
|
+
"tests_run": ["search.test.ts", "debounce.test.ts"],
|
|
111
|
+
"success_criteria_met": true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- **status**: verified | partial | unverified
|
|
117
|
+
- **regression_tests_passed**: Existing tests still pass
|
|
118
|
+
- **user_journey_tested**: Manual testing of the affected feature
|
|
119
|
+
- **tests_run**: Specific test files executed
|
|
120
|
+
- **success_criteria_met**: Bug no longer reproducible
|
|
121
|
+
|
|
122
|
+
### Quality Gates
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"quality_gates": {
|
|
127
|
+
"guardian_validated": true,
|
|
128
|
+
"tested_e2e": false,
|
|
129
|
+
"tested_from_ui": true,
|
|
130
|
+
"security_reviewed": false,
|
|
131
|
+
"architect_reviewed": false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Optional validation checkpoints for higher-stakes fixes.
|
|
137
|
+
|
|
138
|
+
### Tags
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"tags": ["react", "hooks", "useMemo", "debounce", "search"],
|
|
143
|
+
"files_changed": ["src/hooks/useDebounce.ts", "src/components/SearchBar.tsx"],
|
|
144
|
+
"agent_used": "coder"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **tags**: Searchable keywords for categorization
|
|
149
|
+
- **files_changed**: All modified files
|
|
150
|
+
- **agent_used**: Which Claude agent fixed this
|
|
151
|
+
|
|
152
|
+
## Quality Scoring
|
|
153
|
+
|
|
154
|
+
The memory system calculates a quality score (0-100%) based on:
|
|
155
|
+
|
|
156
|
+
| Component | Weight | Criteria |
|
|
157
|
+
|-----------|--------|----------|
|
|
158
|
+
| Root Cause | 30% | Description length, confidence, file reference |
|
|
159
|
+
| Fix | 30% | Approach detail, change documentation |
|
|
160
|
+
| Verification | 20% | Status, tests run, criteria met |
|
|
161
|
+
| Documentation | 20% | Tags, files listed, agent noted |
|
|
162
|
+
|
|
163
|
+
**Quality targets:**
|
|
164
|
+
- 75%+: Excellent - highly reliable for future matching
|
|
165
|
+
- 50-74%: Good - useful but may need verification
|
|
166
|
+
- <50%: Fair - stored but lower priority in results
|
|
167
|
+
|
|
168
|
+
## How Claude Code Stores Incidents
|
|
169
|
+
|
|
170
|
+
Claude Code should **directly write JSON files** to store incidents. No CLI command needed.
|
|
171
|
+
|
|
172
|
+
### Step-by-Step
|
|
173
|
+
|
|
174
|
+
**1. Create directory (if needed):**
|
|
175
|
+
```bash
|
|
176
|
+
mkdir -p .claude/memory/incidents
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**2. Generate incident ID:**
|
|
180
|
+
```
|
|
181
|
+
INC_YYYYMMDD_HHMMSS_xxxx
|
|
182
|
+
```
|
|
183
|
+
Example: `INC_20241231_143052_a7b2` where:
|
|
184
|
+
- `20241231` = date (Dec 31, 2024)
|
|
185
|
+
- `143052` = time (14:30:52)
|
|
186
|
+
- `a7b2` = 4 random alphanumeric chars
|
|
187
|
+
|
|
188
|
+
**3. Write JSON file:**
|
|
189
|
+
```
|
|
190
|
+
.claude/memory/incidents/INC_20241231_143052_a7b2.json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**4. Include required fields:**
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"incident_id": "INC_20241231_143052_a7b2",
|
|
197
|
+
"timestamp": 1735654252000,
|
|
198
|
+
"symptom": "Description of the bug",
|
|
199
|
+
"root_cause": {
|
|
200
|
+
"description": "Technical explanation",
|
|
201
|
+
"file": "path/to/file.ts",
|
|
202
|
+
"category": "logic",
|
|
203
|
+
"confidence": 0.85
|
|
204
|
+
},
|
|
205
|
+
"fix": {
|
|
206
|
+
"approach": "How it was fixed",
|
|
207
|
+
"changes": []
|
|
208
|
+
},
|
|
209
|
+
"tags": ["searchable", "keywords"],
|
|
210
|
+
"quality_score": 0.75
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### When to Store
|
|
215
|
+
|
|
216
|
+
- **After fixing a bug** - Immediately document while context is fresh
|
|
217
|
+
- **After debugging session** - Even if fix wasn't found, document the investigation
|
|
218
|
+
- **After pattern identified** - If you notice recurring issues
|
|
219
|
+
|
|
220
|
+
### Workflow Example
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
1. Bug reported: "Search not working"
|
|
224
|
+
2. Search memory via `debugger search` MCP tool: "search not working"
|
|
225
|
+
3. Found match? → Apply fix
|
|
226
|
+
No match? → Investigate and fix
|
|
227
|
+
4. Store via `debugger store` MCP tool (or write JSON to .claude/memory/incidents/INC_xxx.json)
|
|
228
|
+
5. Future searches will find this incident
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Storage Modes
|
|
232
|
+
|
|
233
|
+
### Local Mode (default)
|
|
234
|
+
|
|
235
|
+
Incidents stored in project's `.claude/memory/incidents/`:
|
|
236
|
+
- Project-specific context
|
|
237
|
+
- Doesn't pollute other projects
|
|
238
|
+
- Best for project-specific bugs
|
|
239
|
+
|
|
240
|
+
### Shared Mode
|
|
241
|
+
|
|
242
|
+
Incidents stored in `.build-loop/issues/`:
|
|
243
|
+
- Cross-project learning
|
|
244
|
+
- Common patterns accessible everywhere
|
|
245
|
+
- Best for reusable solutions
|
|
246
|
+
|
|
247
|
+
Set via environment:
|
|
248
|
+
```bash
|
|
249
|
+
export CLAUDE_MEMORY_MODE=shared
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Retrieval Strategy
|
|
253
|
+
|
|
254
|
+
When searching for matches, the system:
|
|
255
|
+
|
|
256
|
+
1. **Pattern match first** (90% confidence threshold)
|
|
257
|
+
2. **Incident search** (70% confidence threshold)
|
|
258
|
+
3. **Keyword similarity** using Jaccard scoring
|
|
259
|
+
4. **Temporal weighting** - prefer recent incidents (90-day window)
|
|
260
|
+
|
|
261
|
+
Higher quality incidents rank higher in results.
|
|
262
|
+
|
|
263
|
+
## Interactive Documentation
|
|
264
|
+
|
|
265
|
+
Use interactive mode for guided documentation:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { storeIncident } from '<coding-debugger-package>';
|
|
269
|
+
|
|
270
|
+
await storeIncident(incident, {
|
|
271
|
+
interactive: true,
|
|
272
|
+
validate_schema: true
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The system prompts for:
|
|
277
|
+
- Missing required fields
|
|
278
|
+
- Short descriptions that need expansion
|
|
279
|
+
- Verification status confirmation
|
|
280
|
+
- Tag suggestions based on symptom
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
|
|
3
|
+
# iOS Notification / Alarm Diagnostic Playbook
|
|
4
|
+
|
|
5
|
+
Domain reference for diagnosing "the alarm doesn't fire" on iOS apps that use `UNUserNotificationCenter` for timed completion alerts (Pomodoro timers, meditation apps, fasting trackers, sleep alarms, anything with a scheduled future notification).
|
|
6
|
+
|
|
7
|
+
Source: build-loop run on a sample timer app (2026-04-26). Single high-confidence root cause; the playbook generalizes the diagnostic moves.
|
|
8
|
+
|
|
9
|
+
## Smoking-gun pattern
|
|
10
|
+
|
|
11
|
+
> Notification scheduled ONLY at background transition + unconditionally cancelled on every foreground return.
|
|
12
|
+
|
|
13
|
+
If the codebase has both of these, several normal user flows leave nothing armed. Before doing anything else, search for these two patterns; if both exist, you have your root cause in under 60 seconds.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
grep -n "scheduleTimedNotification\|scheduleCompletion" --include="*.swift" -r .
|
|
17
|
+
grep -n "removeAllPendingNotificationRequests\|cancelScheduled" --include="*.swift" -r .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If schedule sites are inside `applicationDidEnterBackground`, `scenePhase == .background`, or a single `handleBackgroundTransition`, **and** cancel sites run on every foreground return without checking timer state, file the finding as `KNOWN_FIX` against `INC_IOS_ALARM_SCHEDULE_TIMING`.
|
|
21
|
+
|
|
22
|
+
## Hypothesis tree (8 priorities)
|
|
23
|
+
|
|
24
|
+
When the smoking-gun pattern doesn't apply, descend in this order. Stop at the first confirmed cause.
|
|
25
|
+
|
|
26
|
+
1. **Schedule timing** — Where is `UNUserNotificationCenterAdd` actually called? If the only call site is a background-transition hook, force-quit / fast suspend / foreground-only sessions never arm.
|
|
27
|
+
2. **Foreground cancel** — Is `removePendingNotificationRequests` called unconditionally on foreground? After fixing #1, this is the new bug.
|
|
28
|
+
3. **Authorization timing** — When is `requestAuthorization` invoked? If "after first completion," the first session runs unauthorized and silently fails to deliver.
|
|
29
|
+
4. **Identifier collisions** — Are multiple call sites adding requests with different UUIDs? Cancellation by-identifier won't catch them; the OS may dedupe or the app may leak pending requests.
|
|
30
|
+
5. **Trigger interval** — Is `timeInterval` ever 0 or negative (clock skew, timer overshoot)? `UNTimeIntervalNotificationTrigger` requires `> 0`.
|
|
31
|
+
6. **Sound asset path** — Is the chime asset actually copied into `Bundle.main`? `UNNotificationSound(named:)` silently falls back to no sound if the file isn't found.
|
|
32
|
+
7. **Delegate registration** — Is `UNUserNotificationCenter.current().delegate` set, and does `willPresent` return `[.banner, .sound]`? If not, foreground delivery is invisible.
|
|
33
|
+
8. **Interruption level + Focus** — Is `interruptionLevel = .timeSensitive` set, and is the iOS Time Sensitive entitlement active? Without it, Focus modes suppress the banner.
|
|
34
|
+
|
|
35
|
+
## Parallel-investigator decomposition
|
|
36
|
+
|
|
37
|
+
For "alarm doesn't fire," dispatch 3 concurrent investigators. None share state.
|
|
38
|
+
|
|
39
|
+
| Investigator | Scope | Files / Symbols |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| **Timer architecture** | Where is the notification scheduled, where is it cancelled, what other paths affect lifetime | `*TimerEngine*.swift`, `*Notification*.swift`, every `scheduleCompletion` / `cancelScheduled*` call site |
|
|
42
|
+
| **Audio + haptic routing** | Foreground completion path, AVAudioSession config, ringer-bypass, CoreHaptics fallbacks | `*AudioService*.swift`, `AudioSessionManager*`, `playChime`, `UINotificationFeedbackGenerator`, `CHHapticEngine` |
|
|
43
|
+
| **Lifecycle + cancel** | scenePhase transitions, app delegate hooks, force-quit handling, ActiveSessionRecovery | `SampleTimerApp+iOS.swift` or `App+iOS.swift`, `applicationWillTerminate`, `scenePhase.onChange`, recovery code |
|
|
44
|
+
|
|
45
|
+
## Symbol search lists
|
|
46
|
+
|
|
47
|
+
Run these in any iOS project as a starting cluster.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Notification lifecycle
|
|
51
|
+
grep -rn "UNUserNotificationCenter\|UNTimeIntervalNotificationTrigger\|UNNotificationRequest" --include="*.swift" .
|
|
52
|
+
grep -rn "removePendingNotificationRequests\|removeAllPendingNotificationRequests" --include="*.swift" .
|
|
53
|
+
|
|
54
|
+
# Authorization
|
|
55
|
+
grep -rn "requestAuthorization\|getNotificationSettings\|UNAuthorizationStatus" --include="*.swift" .
|
|
56
|
+
|
|
57
|
+
# Foreground completion path
|
|
58
|
+
grep -rn "willPresent\|UNNotificationPresentationOptions\|interruptionLevel" --include="*.swift" .
|
|
59
|
+
|
|
60
|
+
# Audio routing
|
|
61
|
+
grep -rn "AVAudioSession\|AVAudioPlayer\|AudioServicesPlaySystemSound" --include="*.swift" .
|
|
62
|
+
|
|
63
|
+
# Haptics
|
|
64
|
+
grep -rn "CHHapticEngine\|UINotificationFeedbackGenerator\|UIImpactFeedbackGenerator" --include="*.swift" .
|
|
65
|
+
|
|
66
|
+
# Lifecycle
|
|
67
|
+
grep -rn "scenePhase\|applicationDidEnterBackground\|applicationWillTerminate" --include="*.swift" .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## File anti-patterns to flag
|
|
71
|
+
|
|
72
|
+
Treat the presence of any of these as a high-priority finding.
|
|
73
|
+
|
|
74
|
+
| Anti-pattern | Where it bites |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `removeAllPendingNotificationRequests()` called outside reset/teardown | Wipes unrelated app notifications, masks identifier mistakes, blocks per-session debugging |
|
|
77
|
+
| Schedule call inside `if scenePhase == .background` only | Force-quit, fast suspend, foreground-only sessions go unarmed |
|
|
78
|
+
| Foreground transition unconditionally calls `cancelScheduledNotifications()` | Lock-then-quick-unlock cancels the alarm while the timer is still running |
|
|
79
|
+
| `requestAuthorization` invoked after first session completion | First session is silent; user-perceived "alarm broken" |
|
|
80
|
+
| `AVAudioPlayer` chime with no fallback to `.default` | Missing or corrupted bundled asset = silent foreground completion |
|
|
81
|
+
| `UNNotificationSound(named: "chime.caf")` without `Bundle.main.url(forResource:withExtension:)` check | Silent fail at delivery time, no log |
|
|
82
|
+
| Single delegate impl on a multi-platform target without `#if os(...)` for `badgeSetting`, `UIImpactFeedbackGenerator`, etc. | watchOS / macOS build breaks; or worse, runtime crashes |
|
|
83
|
+
| `UIBackgroundModes: [audio]` to keep the timer "alive" | App Store rejection; not the fix |
|
|
84
|
+
|
|
85
|
+
## Verification matrix (15-state iPhone)
|
|
86
|
+
|
|
87
|
+
Tier the matrix by what each state actually exercises. **Sim-verifiable** runs in `xcrun simctl`; **real-device** needs hardware.
|
|
88
|
+
|
|
89
|
+
| # | Scenario | Expected | Sim | Device |
|
|
90
|
+
|---|---|---|---|---|
|
|
91
|
+
| 1 | Foreground tick to zero | banner + sound + haptic | sound | haptic |
|
|
92
|
+
| 2 | Background then zero | banner + sound while locked | ✅ | ✅ |
|
|
93
|
+
| 3 | Locked then zero | lock-screen banner + sound | ✅ | ✅ |
|
|
94
|
+
| 4 | Force-quit then zero | banner still fires | ✅ | ✅ |
|
|
95
|
+
| 5 | Pause → resume → zero | fires at new endDate | ✅ | ✅ |
|
|
96
|
+
| 6 | Reset mid-session | no banner | ✅ | ✅ |
|
|
97
|
+
| 7 | Scrub: bg → fg → bg → zero | banner fires once | ✅ | ✅ |
|
|
98
|
+
| 8 | Focus mode allowed | banner fires | ✅ | ✅ |
|
|
99
|
+
| 9 | Focus mode blocked | banner suppressed (expected) | ✅ | ✅ |
|
|
100
|
+
| 10 | Silent switch on | sound through (AVAudioSession.playback) | n/a | device-only |
|
|
101
|
+
| 11 | DND on | banner suppressed unless Critical Alerts | n/a | device-only |
|
|
102
|
+
| 12 | Permissions: notDetermined | first Start triggers prompt | ✅ | ✅ |
|
|
103
|
+
| 13 | Permissions: denied | no banner, no crash, log warns | ✅ | ✅ |
|
|
104
|
+
| 14 | Backgrounded > 90 min | recovery flow | ✅ | ✅ |
|
|
105
|
+
| 15 | Watch session running concurrently | iPhone alarm independent | partial | ✅ |
|
|
106
|
+
|
|
107
|
+
## Diagnostic logging template
|
|
108
|
+
|
|
109
|
+
Every iOS notification fix should ship with structured logging on day 1, not as a follow-up. Recommended subsystem and category:
|
|
110
|
+
|
|
111
|
+
```swift
|
|
112
|
+
import os.log
|
|
113
|
+
|
|
114
|
+
enum AlertDiagnostics {
|
|
115
|
+
private static let logger = Logger(
|
|
116
|
+
subsystem: Bundle.main.bundleIdentifier ?? "com.example",
|
|
117
|
+
category: "alert-diag"
|
|
118
|
+
)
|
|
119
|
+
static func scheduled(identifier: String, afterSeconds: Int, source: String) { /* ... */ }
|
|
120
|
+
static func cancelled(identifier: String, source: String) { /* ... */ }
|
|
121
|
+
static func cancelSkipped(reason: String, source: String) { /* ... */ }
|
|
122
|
+
static func scenePhase(_ phase: String) { /* ... */ }
|
|
123
|
+
static func dumpAuthSettings() { /* ... */ }
|
|
124
|
+
static func dumpPendingRequests(reason: String) { /* ... */ }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Filter Console.app on `subsystem:com.example category:alert-diag` to read the trail. The `[alert-diag]` prefix and `id=...` tokens make grep one-shot.
|
|
129
|
+
|
|
130
|
+
## Verdict templates
|
|
131
|
+
|
|
132
|
+
When this playbook applies, use one of the following incident IDs.
|
|
133
|
+
|
|
134
|
+
- `INC_IOS_ALARM_SCHEDULE_TIMING` — schedule call site only at background transition
|
|
135
|
+
- `INC_IOS_ALARM_UNCONDITIONAL_CANCEL` — foreground cancel runs while timer still active
|
|
136
|
+
- `INC_IOS_ALARM_AUTH_LATE` — requestAuthorization after first completion
|
|
137
|
+
- `INC_IOS_ALARM_CHIME_MISSING` — bundled audio asset not in Bundle.main
|
|
138
|
+
- `INC_IOS_ALARM_FOCUS_BLOCKED` — interruption level missing or Focus suppressing
|
|
139
|
+
|
|
140
|
+
## Confidence
|
|
141
|
+
|
|
142
|
+
This playbook is built from one canonical incident (sample timer app, 2026-04-26). Promote to "pattern" status (`PTN_IOS_ALARM_*`) once 3+ incidents share the smoking-gun signature.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Multi-alert identifier-set pattern
|
|
147
|
+
|
|
148
|
+
Added 2026-04-26 (sample timer app build 72) when alert customization landed: count, persistence (re-fire every N seconds), and backup escalation expanded the per-transition notification stack from 1 request to up to ~10. The schedule and cancel paths must agree on the identifier set or the cancel will silently leak requests.
|
|
149
|
+
|
|
150
|
+
### The bug pattern this prevents
|
|
151
|
+
|
|
152
|
+
> User changes alert count from 3 to 1 mid-session. Pause cancels with the legacy single-identifier `[timerCompletionIdentifier]`. The other two requests survive. Pomodoro fires three notifications during a paused session — exactly the failure mode persistence+backup were trying to avoid.
|
|
153
|
+
|
|
154
|
+
The same shape appears whenever a feature changes the cardinality or naming of pending requests (persistence toggles, A/B sound tests, multi-stage celebrations). The cancel path is written first against a smaller cardinality, then someone bumps the cardinality on the schedule side without updating cancel.
|
|
155
|
+
|
|
156
|
+
### The rule
|
|
157
|
+
|
|
158
|
+
**Schedule and cancel must call a single identifier-set generator function, parameterized by the same inputs.** Any feature that adds requests to a session bumps the cardinality through that one function, and both sides update for free.
|
|
159
|
+
|
|
160
|
+
```swift
|
|
161
|
+
static func pomodoroNotificationIdentifiers(sessionTag: String, config: AlertConfig) -> [String] {
|
|
162
|
+
var ids: [String] = [timerCompletionIdentifier] // legacy single id, always cleared
|
|
163
|
+
let prefix = "\(timerCompletionIdentifier).tag.\(sessionTag)"
|
|
164
|
+
for i in 0..<max(1, min(3, config.count)) {
|
|
165
|
+
ids.append("\(prefix).alert.\(i)")
|
|
166
|
+
}
|
|
167
|
+
if config.persistenceEnabled {
|
|
168
|
+
let n = min(6, max(0, config.persistenceMaxSec / max(15, config.persistenceIntervalSec)))
|
|
169
|
+
for j in 0..<n { ids.append("\(prefix).persist.\(j)") }
|
|
170
|
+
}
|
|
171
|
+
return ids
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`scheduleCompletion(...)` calls this and adds requests under each id. `cancelScheduledCompletion(config:sessionTag:source:)` calls the same function and passes the array to `removePendingNotificationRequests(withIdentifiers:)`. The legacy bare identifier stays in the set forever so a clean upgrade absorbs any in-flight build-71 request.
|
|
176
|
+
|
|
177
|
+
### Caller responsibility
|
|
178
|
+
|
|
179
|
+
The caller (TimerEngine) remembers `armedSessionTag` and `armedConfig` at schedule time and passes them back at cancel time:
|
|
180
|
+
|
|
181
|
+
```swift
|
|
182
|
+
private func armCompletionStack(...) {
|
|
183
|
+
let config = alertSettings.config(forBreak: forBreak)
|
|
184
|
+
let tag = currentSessionTag(forBreak: forBreak)
|
|
185
|
+
armedSessionTag = tag
|
|
186
|
+
armedConfig = config
|
|
187
|
+
delegate?.scheduleTimedNotificationWithConfig(..., config: config, sessionTag: tag, ...)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func cancelCompletionStack(source: String) {
|
|
191
|
+
if let tag = armedSessionTag, let config = armedConfig {
|
|
192
|
+
delegate?.cancelScheduledNotificationsWithConfig(config: config, sessionTag: tag, source: source)
|
|
193
|
+
} else {
|
|
194
|
+
delegate?.cancelScheduledNotifications(source: source) // legacy fallback
|
|
195
|
+
}
|
|
196
|
+
armedSessionTag = nil
|
|
197
|
+
armedConfig = nil
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`sessionTag` is derived from `phaseStartDate` so re-arming during a phase keeps the same tag (idempotent replace), but a new phase gets a fresh tag (so the prior phase's stack is not accidentally cancelled by a subsequent cancel call).
|
|
202
|
+
|
|
203
|
+
### Diagnostics
|
|
204
|
+
|
|
205
|
+
Log the full identifier list, not the count alone, on every schedule and cancel. `[alert-diag] schedule-stack ids=[a,b,c,...] cfg-count=3 persist=true` is greppable; "scheduled 3 notifications" is not.
|
|
206
|
+
|
|
207
|
+
### Smoking gun for the bug
|
|
208
|
+
|
|
209
|
+
`getPendingNotificationRequests` returns N entries after a "cancel everything" lifecycle hook (pause, reset, foreground-not-running). The fix is always: route the cancel through the same generator the schedule used.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## iOS Local Network discovery checklist
|
|
214
|
+
|
|
215
|
+
Added 2026-04-26 (sample timer app build 72) when "Listener failed: NWError -65569 DefunctConnection" appeared in Profile → Nearby Devices. iOS Local Network has three independent failure modes; check them in this order.
|
|
216
|
+
|
|
217
|
+
### 1. Plist stack (one-time setup, easy to forget)
|
|
218
|
+
|
|
219
|
+
```xml
|
|
220
|
+
<!-- Info.plist for iOS, INFOPLIST_KEY_* in xcconfig/project.yml for macOS -->
|
|
221
|
+
<key>NSLocalNetworkUsageDescription</key>
|
|
222
|
+
<string>Why your app needs the local network — user-readable, shown in the system permission prompt.</string>
|
|
223
|
+
<key>NSBonjourServices</key>
|
|
224
|
+
<array>
|
|
225
|
+
<string>_yourapp._tcp</string>
|
|
226
|
+
</array>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
If `NSLocalNetworkUsageDescription` is missing, iOS 14+ kills the listener on creation and the user never sees a permission prompt — so they cannot grant access. Symptom is identical to "permission denied". The service type in `NSBonjourServices` MUST match the string passed to `NWBrowser` and `NWListener.Service(type:)` exactly. A typo silently produces empty browser results.
|
|
230
|
+
|
|
231
|
+
### 2. NWListener / NWBrowser retain rule
|
|
232
|
+
|
|
233
|
+
Listener and browser must be retained as instance properties:
|
|
234
|
+
|
|
235
|
+
```swift
|
|
236
|
+
final class LocalNetworkSync {
|
|
237
|
+
private var listener: NWListener? // strong reference required
|
|
238
|
+
private var browser: NWBrowser?
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A local `let listener = try NWListener(...)` inside a function is destroyed at scope exit. The mDNSResponder XPC connection drops with it, the user sees nothing, and the bug presents as "no devices found" with no error path.
|
|
243
|
+
|
|
244
|
+
### 3. Distinguish -65569 (transient) from -65570 (permission)
|
|
245
|
+
|
|
246
|
+
| Code | NWError shape | Meaning | Action |
|
|
247
|
+
|--------|-----------------------|-------------------------------------|---------------------------------|
|
|
248
|
+
| -65569 | `.dns(-65569)` | DefunctConnection — mDNSResponder XPC dropped (wifi change, simulator quirk, suspension) | Restart with bounded backoff |
|
|
249
|
+
| -65570 | `.dns(-65570)` | Permission denied / not granted | Show "Open Settings" — DO NOT restart |
|
|
250
|
+
|
|
251
|
+
The historical fix of "show NWError.localizedDescription" surfaces both as raw jargon. Map them:
|
|
252
|
+
|
|
253
|
+
```swift
|
|
254
|
+
nonisolated private static func isPermissionDenied(_ error: NWError) -> Bool {
|
|
255
|
+
if case .dns(let code) = error, Int(code) == -65570 { return true }
|
|
256
|
+
return false
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
UI translation: -65569 should say "Searching… reconnecting after a network change." (recoverable). -65570 should show a permission card with an Open Settings button (`UIApplication.openSettingsURLString`).
|
|
261
|
+
|
|
262
|
+
### 4. Restart-on-defunct backoff
|
|
263
|
+
|
|
264
|
+
`.failed(-65569)` on a retained listener requires recreation — the existing `NWListener` instance is permanently dead. Bounded backoff prevents battery drain on a permanently broken environment:
|
|
265
|
+
|
|
266
|
+
```swift
|
|
267
|
+
private static let restartDelaysSec: [Double] = [1.0, 3.0, 8.0]
|
|
268
|
+
private var listenerRestartAttempts: Int = 0
|
|
269
|
+
|
|
270
|
+
private func scheduleListenerRestart() {
|
|
271
|
+
let delay = Self.restartDelaysSec[min(listenerRestartAttempts, Self.restartDelaysSec.count - 1)]
|
|
272
|
+
listenerRestartAttempts += 1
|
|
273
|
+
Task { @MainActor [weak self] in
|
|
274
|
+
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
|
275
|
+
guard let self, self.isActive, !self.permissionDenied else { return }
|
|
276
|
+
self.listener?.cancel()
|
|
277
|
+
self.listener = nil
|
|
278
|
+
self.startListener()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Reset `listenerRestartAttempts = 0` on `.ready` so a successful restart re-enables fast retry on the next failure. **Suspend restart while `permissionDenied` is true** — there is no point restarting a listener the system is going to kill again until the user grants permission.
|
|
284
|
+
|
|
285
|
+
### 5. Smoking gun signatures
|
|
286
|
+
|
|
287
|
+
| Symptom | Cause |
|
|
288
|
+
|------------------------------------------------------------------------|--------------------------------------|
|
|
289
|
+
| Listener fails on first launch, no permission prompt ever shown | Missing `NSLocalNetworkUsageDescription` |
|
|
290
|
+
| Browser results always empty despite peers running | Service type mismatch between Plist and code |
|
|
291
|
+
| Listener works in foreground, dies after backgrounding | Listener stored in a local, not retained |
|
|
292
|
+
| -65569 appears intermittently after wifi changes / app resume | Normal — needs restart-on-defunct backoff |
|
|
293
|
+
| -65570 with no in-Settings entry for the app | Plist string was added after first launch — uninstall/reinstall to retrigger |
|
|
294
|
+
|
|
295
|
+
### Incident IDs
|
|
296
|
+
|
|
297
|
+
- `INC_IOS_LOCAL_NETWORK_DEFUNCT` — -65569 with no restart logic
|
|
298
|
+
- `INC_IOS_LOCAL_NETWORK_PLIST` — missing usage description or Bonjour service entry
|
|
299
|
+
- `INC_IOS_LOCAL_NETWORK_RETAIN` — listener/browser destroyed at scope exit
|
|
300
|
+
- `INC_IOS_LOCAL_NETWORK_PERMISSION_UI` — raw NWError shown to user instead of actionable Open Settings prompt
|