@event4u/agent-config 14.22.0 → 15.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +333 -356
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +8 -7
- package/dist/agent-src/commands/agents/init.md +1 -1
- package/dist/agent-src/commands/agents/user/accept.md +4 -4
- package/dist/agent-src/commands/agents/user/delete.md +2 -2
- package/dist/agent-src/commands/agents/user/init.md +3 -3
- package/dist/agent-src/commands/agents/user/review.md +3 -3
- package/dist/agent-src/commands/agents/user/show.md +3 -3
- package/dist/agent-src/commands/agents/user/update.md +2 -2
- package/dist/agent-src/commands/agents/user.md +6 -6
- package/dist/agent-src/commands/analytics/prune.md +1 -1
- package/dist/agent-src/commands/analytics/show.md +2 -2
- package/dist/agent-src/commands/analyze/decision.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +5 -5
- package/dist/agent-src/commands/analyze/incident.md +3 -3
- package/dist/agent-src/commands/analyze/near-miss.md +3 -3
- package/dist/agent-src/commands/analyze/postmortem.md +4 -4
- package/dist/agent-src/commands/analyze/premortem.md +2 -2
- package/dist/agent-src/commands/analyze.md +2 -2
- package/dist/agent-src/commands/bug/fix.md +1 -1
- package/dist/agent-src/commands/bug/investigate.md +2 -2
- package/dist/agent-src/commands/cost/profile.md +3 -3
- package/dist/agent-src/commands/design.md +1 -1
- package/dist/agent-src/commands/feature/explore.md +1 -1
- package/dist/agent-src/commands/feature/plan.md +2 -2
- package/dist/agent-src/commands/feature/roadmap.md +2 -2
- package/dist/agent-src/commands/ghostwriter/fetch.md +4 -4
- package/dist/agent-src/commands/ghostwriter/list.md +1 -1
- package/dist/agent-src/commands/ghostwriter/show.md +1 -1
- package/dist/agent-src/commands/ghostwriter/write.md +9 -9
- package/dist/agent-src/commands/humanize.md +6 -6
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/judge/solo.md +1 -1
- package/dist/agent-src/commands/judge.md +1 -1
- package/dist/agent-src/commands/knowledge/cross-repo.md +1 -1
- package/dist/agent-src/commands/knowledge/forget.md +1 -1
- package/dist/agent-src/commands/knowledge/ingest.md +2 -2
- package/dist/agent-src/commands/knowledge/list.md +1 -1
- package/dist/agent-src/commands/memory/add.md +3 -3
- package/dist/agent-src/commands/memory/load.md +4 -4
- package/dist/agent-src/commands/memory/mine-session.md +1 -1
- package/dist/agent-src/commands/memory/promote.md +4 -4
- package/dist/agent-src/commands/memory/propose.md +2 -2
- package/dist/agent-src/commands/mode.md +5 -5
- package/dist/agent-src/commands/post-as/ghostwriter.md +3 -3
- package/dist/agent-src/commands/post-as/me.md +7 -7
- package/dist/agent-src/commands/pr/create.md +1 -1
- package/dist/agent-src/commands/pr/merge.md +48 -39
- package/dist/agent-src/commands/profile/activate.md +1 -1
- package/dist/agent-src/commands/profile/show.md +1 -1
- package/dist/agent-src/commands/research/deep.md +2 -2
- package/dist/agent-src/commands/research/report.md +2 -2
- package/dist/agent-src/commands/review/changes.md +1 -1
- package/dist/agent-src/commands/review/routing.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +2 -2
- package/dist/agent-src/commands/roadmap/next.md +9 -1
- package/dist/agent-src/commands/roadmap/process-full.md +2 -2
- package/dist/agent-src/commands/security-audit-config.md +3 -3
- package/dist/agent-src/commands/skill/preview.md +1 -1
- package/dist/agent-src/commands/skills/discover.md +1 -1
- package/dist/agent-src/commands/sync/agent-settings.md +2 -2
- package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
- package/dist/agent-src/commands/tests/create.md +1 -1
- package/dist/agent-src/commands/tests/e2e-heal.md +1 -1
- package/dist/agent-src/commands/tests/e2e-plan.md +1 -1
- package/dist/agent-src/contexts/authority/kernel-rule-edits.md +6 -6
- package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -2
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +2 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +88 -1
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +1 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +1 -1
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +13 -1
- package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +1 -1
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +3 -3
- package/dist/agent-src/contexts/execution/mandated-lines.md +1 -1
- package/dist/agent-src/contexts/execution/non-interactive-contract.md +1 -1
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -1
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +5 -5
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +2 -2
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +1 -1
- package/dist/agent-src/contexts/execution/user-memory-channels.md +3 -3
- package/dist/agent-src/contexts/execution/verification-mechanics.md +1 -1
- package/dist/agent-src/contexts/judges/persona-voice-rubric.md +2 -2
- package/dist/agent-src/guidelines/abstraction-thresholds.md +88 -0
- package/dist/agent-src/guidelines/agent-infra/5w2h-analysis.md +260 -0
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +99 -0
- package/dist/agent-src/guidelines/agent-infra/agent-interaction-and-decision-quality.md +325 -0
- package/dist/agent-src/guidelines/agent-infra/api-cost-levers.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +222 -0
- package/dist/agent-src/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/asking-and-brevity-examples.md +198 -0
- package/dist/agent-src/guidelines/agent-infra/break-glass-usage.md +113 -0
- package/dist/agent-src/guidelines/agent-infra/carve-out-predicates.md +17 -0
- package/dist/agent-src/guidelines/agent-infra/comparison-matrix.md +179 -0
- package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +165 -0
- package/dist/agent-src/guidelines/agent-infra/corpus-grounding-authoring.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/critical-thinking.md +156 -0
- package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/developer-judgment.md +82 -0
- package/dist/agent-src/guidelines/agent-infra/direct-answers-demos.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/domain-adoption-gates.md +112 -0
- package/dist/agent-src/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
- package/dist/agent-src/guidelines/agent-infra/domain-pack-architecture.md +56 -0
- package/dist/agent-src/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/emphasis-budget.md +45 -0
- package/dist/agent-src/guidelines/agent-infra/engineering-memory-data-format.md +210 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
- package/dist/agent-src/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/failure-signatures.md +70 -0
- package/dist/agent-src/guidelines/agent-infra/false-green.md +233 -0
- package/dist/agent-src/guidelines/agent-infra/first-principles.md +192 -0
- package/dist/agent-src/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +169 -0
- package/dist/agent-src/guidelines/agent-infra/gate-authoring.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/dist/agent-src/guidelines/agent-infra/installed-tools-manifest.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/inversion-thinking.md +388 -0
- package/dist/agent-src/guidelines/agent-infra/ios-simulator-guide.md +378 -0
- package/dist/agent-src/guidelines/agent-infra/language-and-tone-examples.md +141 -0
- package/dist/agent-src/guidelines/agent-infra/layered-settings.md +317 -0
- package/dist/agent-src/guidelines/agent-infra/linked-projects-onboarding-gate.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/mcp-request-signing.md +197 -0
- package/dist/agent-src/guidelines/agent-infra/memory-access.md +149 -0
- package/dist/agent-src/guidelines/agent-infra/mental-models.md +314 -0
- package/dist/agent-src/guidelines/agent-infra/minimal-safe-diff-mechanics.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/missing-tool-handling.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/model-recommendation.md +101 -0
- package/dist/agent-src/guidelines/agent-infra/naming.md +69 -0
- package/dist/agent-src/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/dist/agent-src/guidelines/agent-infra/output-patterns.md +117 -0
- package/dist/agent-src/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/dist/agent-src/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/dist/agent-src/guidelines/agent-infra/review-routing-data-format.md +143 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +274 -0
- package/dist/agent-src/guidelines/agent-infra/role-contracts.md +209 -0
- package/dist/agent-src/guidelines/agent-infra/role-mode-router.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/runtime-layer.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/scqa-framework.md +526 -0
- package/dist/agent-src/guidelines/agent-infra/security-lint-containment.md +121 -0
- package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/simplicity-and-goal-demos.md +211 -0
- package/dist/agent-src/guidelines/agent-infra/six-hats.md +353 -0
- package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +229 -0
- package/dist/agent-src/guidelines/agent-infra/skill-quality-checklist.md +157 -0
- package/dist/agent-src/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/dist/agent-src/guidelines/agent-infra/systems-thinking.md +220 -0
- package/dist/agent-src/guidelines/agent-infra/tool-description-as-policy.md +35 -0
- package/dist/agent-src/guidelines/agent-infra/tool-integration.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/dist/agent-src/guidelines/agent-infra/untrusted-input-spotlighting.md +166 -0
- package/dist/agent-src/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
- package/dist/agent-src/guidelines/augment-portability-patterns.md +70 -0
- package/dist/agent-src/guidelines/code-clarity.md +350 -0
- package/dist/agent-src/guidelines/component-oriented-and-oop-development.md +189 -0
- package/dist/agent-src/guidelines/cross-role-handoff.md +127 -0
- package/dist/agent-src/guidelines/design-antipatterns-triggers.json +18 -0
- package/dist/agent-src/guidelines/design-antipatterns.md +327 -0
- package/dist/agent-src/guidelines/design-asset-discipline.md +38 -0
- package/dist/agent-src/guidelines/design-canon.md +63 -0
- package/dist/agent-src/guidelines/design-fidelity-mechanics.md +293 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +78 -0
- package/dist/agent-src/guidelines/design-handover-extraction.md +117 -0
- package/dist/agent-src/guidelines/design-modes.md +143 -0
- package/dist/agent-src/guidelines/design-reconciliation.md +172 -0
- package/dist/agent-src/guidelines/docs/readme-size-and-splitting.md +209 -0
- package/dist/agent-src/guidelines/e2e/playwright.md +363 -0
- package/dist/agent-src/guidelines/gtm-handoff.md +114 -0
- package/dist/agent-src/guidelines/monorepo-antipatterns.md +141 -0
- package/dist/agent-src/guidelines/php/api-design.md +115 -0
- package/dist/agent-src/guidelines/php/artisan-commands.md +81 -0
- package/dist/agent-src/guidelines/php/blade-ui.md +78 -0
- package/dist/agent-src/guidelines/php/controllers.md +90 -0
- package/dist/agent-src/guidelines/php/database.md +194 -0
- package/dist/agent-src/guidelines/php/eloquent.md +208 -0
- package/dist/agent-src/guidelines/php/flux.md +80 -0
- package/dist/agent-src/guidelines/php/general.md +207 -0
- package/dist/agent-src/guidelines/php/git.md +164 -0
- package/dist/agent-src/guidelines/php/jobs.md +111 -0
- package/dist/agent-src/guidelines/php/livewire.md +71 -0
- package/dist/agent-src/guidelines/php/logging.md +79 -0
- package/dist/agent-src/guidelines/php/naming.md +89 -0
- package/dist/agent-src/guidelines/php/patterns/dependency-injection.md +57 -0
- package/dist/agent-src/guidelines/php/patterns/dtos.md +199 -0
- package/dist/agent-src/guidelines/php/patterns/events.md +67 -0
- package/dist/agent-src/guidelines/php/patterns/factory.md +53 -0
- package/dist/agent-src/guidelines/php/patterns/pipelines.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/policies.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/repositories.md +122 -0
- package/dist/agent-src/guidelines/php/patterns/service-layer.md +64 -0
- package/dist/agent-src/guidelines/php/patterns/strategy.md +247 -0
- package/dist/agent-src/guidelines/php/patterns.md +28 -0
- package/dist/agent-src/guidelines/php/performance.md +92 -0
- package/dist/agent-src/guidelines/php/php-coding-patterns.md +80 -0
- package/dist/agent-src/guidelines/php/resources.md +100 -0
- package/dist/agent-src/guidelines/php/security.md +110 -0
- package/dist/agent-src/guidelines/php/sql.md +97 -0
- package/dist/agent-src/guidelines/php/validations.md +119 -0
- package/dist/agent-src/guidelines/php/websocket.md +100 -0
- package/dist/agent-src/guidelines/prompt-templates.md +189 -0
- package/dist/agent-src/guidelines/redundancy-taxonomy.md +208 -0
- package/dist/agent-src/guidelines/wing4-handoff.md +127 -0
- package/dist/agent-src/personas/README.md +1 -1
- package/dist/agent-src/personas/_template-specialist/persona.md +1 -1
- package/dist/agent-src/personas/product-owner.md +1 -1
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/architecture.md +2 -2
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/ask-when-uncertain.md +2 -2
- package/dist/agent-src/rules/brand-source-of-truth.md +9 -7
- package/dist/agent-src/rules/code-comment-discipline.md +2 -2
- package/dist/agent-src/rules/code-provenance.md +1 -1
- package/dist/agent-src/rules/content-quoting-floor.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +5 -5
- package/dist/agent-src/rules/cross-source-consistency.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +36 -46
- package/dist/agent-src/rules/direct-answers.md +2 -2
- package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
- package/dist/agent-src/rules/evaluator-independence.md +1 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
- package/dist/agent-src/rules/icon-consistency.md +5 -1
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/language-and-tone.md +3 -3
- package/dist/agent-src/rules/minimal-safe-diff.md +1 -1
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/role-mode-adherence.md +2 -2
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/size-enforcement.md +1 -1
- package/dist/agent-src/rules/source-confidentiality.md +2 -2
- package/dist/agent-src/rules/think-before-action.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +7 -5
- package/dist/agent-src/rules/ui-audit-gate.md +3 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/dist/agent-src/skills/activation-design/SKILL.md +2 -2
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +1 -1
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +1 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +24 -3
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
- package/dist/agent-src/skills/churn-prevention/SKILL.md +2 -2
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
- package/dist/agent-src/skills/code-refactoring/SKILL.md +3 -3
- package/dist/agent-src/skills/code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/command-writing/SKILL.md +3 -3
- package/dist/agent-src/skills/comp-banding/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-moat-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -2
- package/dist/agent-src/skills/contracts-cognition/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +2 -2
- package/dist/agent-src/skills/customer-research/SKILL.md +2 -2
- package/dist/agent-src/skills/dashboard-design/SKILL.md +1 -1
- package/dist/agent-src/skills/data-handling-judgment/SKILL.md +1 -1
- package/dist/agent-src/skills/deal-qualification-meddic/SKILL.md +2 -2
- package/dist/agent-src/skills/decision-record/SKILL.md +3 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +8 -8
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +22 -22
- package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +4 -4
- package/dist/agent-src/skills/design-system-capture/references/design-system-json.md +20 -1
- package/dist/agent-src/skills/design-tokens/SKILL.md +17 -2
- package/dist/agent-src/skills/design-variations/SKILL.md +2 -2
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +2 -2
- package/dist/agent-src/skills/discovery-interview/SKILL.md +2 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +3 -3
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -2
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +44 -1
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +2 -2
- package/dist/agent-src/skills/expansion-playbook/SKILL.md +2 -2
- package/dist/agent-src/skills/experiment-loop/SKILL.md +1 -1
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +3 -3
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +1 -1
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +3 -3
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +1 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/forecast-accuracy/SKILL.md +2 -2
- package/dist/agent-src/skills/forecasting/SKILL.md +2 -2
- package/dist/agent-src/skills/fundraising-narrative/SKILL.md +2 -2
- package/dist/agent-src/skills/funnel-analysis/SKILL.md +2 -2
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/gtm-launch/SKILL.md +2 -2
- package/dist/agent-src/skills/hiring-loop-design/SKILL.md +1 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +5 -5
- package/dist/agent-src/skills/humanizer/SKILL.md +1 -1
- package/dist/agent-src/skills/humanizer/data/patterns.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +3 -3
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/launch-readiness/SKILL.md +3 -3
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +6 -6
- package/dist/agent-src/skills/livewire/SKILL.md +1 -1
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +3 -3
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/onboarding-design/SKILL.md +2 -2
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/one-on-one-cadence/SKILL.md +1 -1
- package/dist/agent-src/skills/org-design/SKILL.md +1 -1
- package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +3 -3
- package/dist/agent-src/skills/override-management/SKILL.md +1 -1
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-coder/SKILL.md +1 -1
- package/dist/agent-src/skills/pipeline-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -3
- package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/positioning-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/privacy-review/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -2
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -2
- package/dist/agent-src/skills/retention-loops/SKILL.md +2 -2
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-management/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/references/archival.md +1 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
- package/dist/agent-src/skills/rule-writing/SKILL.md +6 -6
- package/dist/agent-src/skills/runway-cognition/SKILL.md +2 -2
- package/dist/agent-src/skills/scenario-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/references/procedure.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +3 -3
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -3
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +1 -1
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +60 -4
- package/dist/agent-src/skills/throughput-vs-morale-tradeoff/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +28 -8
- package/dist/agent-src/skills/unit-economics-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +2 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
- package/dist/agent-src/skills/vision-articulation/SKILL.md +1 -1
- package/dist/agent-src/skills/voc-extract/SKILL.md +2 -2
- package/dist/agent-src/skills/voice-and-tone-design/SKILL.md +2 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +1 -1
- package/dist/agent-src/skills/workspace-link/SKILL.md +1 -1
- package/dist/agent-src/templates/agent-settings.md +6 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-pages.md +2 -3
- package/dist/agent-src/templates/copilot-instructions.md +1 -1
- package/dist/agent-src/templates/roadmaps.md +1 -1
- package/dist/agent-src/templates/scripts/README.md +2 -2
- package/dist/agent-src/user-types/_template/user-type.md +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
- package/dist/cli-delegate/{chunk-NLP54U3J.js → chunk-2XY7ILBT.js} +13 -1
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-3FKR64R5.js} +34 -6
- package/dist/cli-delegate/{chunk-J3RFNKG4.js → chunk-55ACLDUE.js} +2 -2
- package/dist/cli-delegate/{chunk-PKOBQADP.js → chunk-57PN4GYJ.js} +1 -1
- package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-AK6HODNQ.js} +4 -4
- package/dist/cli-delegate/{chunk-2FTMENJL.js → chunk-GYC3VRYN.js} +1 -1
- package/dist/cli-delegate/{chunk-LWTYTX6F.js → chunk-HZ52BRGG.js} +24 -18
- package/dist/cli-delegate/{chunk-6RJA2F5A.js → chunk-IVBPRCQO.js} +1 -1
- package/dist/cli-delegate/{chunk-I6RHFQEG.js → chunk-YUM5KURG.js} +17 -1
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +24 -16
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_get.js +15 -9
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +41 -11
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +27 -80
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +5 -7
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +4 -6
- package/dist/hooks/dispatch.js +9249 -8981
- package/dist/install/install.mjs +47 -8
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +5496 -2400
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +73 -0
- package/dist/scripts/_lib/agent_src.js +20 -0
- package/dist/scripts/_lib/guidelines_lane.js +158 -0
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +22 -25
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/io/yamlIO.js +125 -17
- package/dist/server/schemas/settings.js +32 -5
- package/dist/shared/settingsSurface.js +6 -1
- package/docs/MIGRATION.md +56 -0
- package/docs/contracts/settings-classes.md +48 -7
- package/docs/guidelines/abstraction-thresholds.md +35 -0
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +3 -3
- package/docs/guidelines/agent-infra/false-green.md +2 -2
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +1 -1
- package/docs/guidelines/design-fidelity-mechanics.md +4 -0
- package/docs/guidelines/design-fidelity-routing.md +78 -0
- package/docs/guidelines/design-reconciliation.md +172 -0
- package/docs/guidelines/gtm-handoff.md +2 -2
- package/docs/guidelines/wing4-handoff.md +2 -2
- package/llms.txt +1 -1
- package/package.json +7 -3
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
- package/src/config/agent-settings.template.yml +94 -1
- package/src/config/ci-local-parity.yml +34 -0
- package/src/config/continuity-surface.json +8 -29
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +31 -8
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +8 -7
- package/src/config/host-injection-effect.json +2 -2
- package/src/config/pack-size-budget.json +55 -4
- package/src/config/packed-binary-manifest.json +1 -1
- package/src/config/platform-anchor.json +47 -0
- package/src/config/preamble-payload-budget.json +2 -3
- package/src/config/publish-surface.json +4 -2
- package/src/config/ratification-policy.json +7 -0
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/config/rule-stub-ceilings.json +16 -9
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/cmd_settings_get.ts +22 -7
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_lib/agent_settings.ts +85 -0
- package/src/scripts/_lib/agent_src.ts +21 -0
- package/src/scripts/_lib/artifact_maturity.ts +242 -0
- package/src/scripts/_lib/conformance_report.ts +403 -0
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_writer.ts +312 -0
- package/src/scripts/_lib/design_system_import.ts +20 -0
- package/src/scripts/_lib/design_tolerance.ts +281 -0
- package/src/scripts/_lib/guidelines_lane.ts +164 -0
- package/src/scripts/_lib/hook_settings.ts +85 -0
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/obligation_frequency.ts +2 -3
- package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
- package/src/scripts/_lib/platform_anchor.ts +1053 -0
- package/src/scripts/_lib/ratification_artifact.ts +278 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/security_lint.ts +23 -2
- package/src/scripts/_lib/self_repair.ts +21 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +387 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +220 -0
- package/src/scripts/_lib/tolerance_shadow.ts +177 -0
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/ai_council/redact_low_impact_entry.ts +8 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/autonomy_friction_traffic.ts +379 -0
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_kernel_edit_ratified.ts +462 -0
- package/src/scripts/check_loss_class_declared.ts +793 -46
- package/src/scripts/check_platform_anchor.ts +390 -0
- package/src/scripts/check_preamble_payload_budget.ts +424 -7
- package/src/scripts/check_projected_rule_routes.ts +395 -0
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +79 -0
- package/src/scripts/code_graph/cli.ts +264 -7
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/query.ts +25 -2
- package/src/scripts/code_graph/sqlite_store.ts +16 -1
- package/src/scripts/code_graph/types.ts +52 -3
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +22 -26
- package/src/scripts/generate_host_cost_table.ts +354 -0
- package/src/scripts/handoff_context_hook.ts +39 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +59 -28
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/category_a.ts +1 -1
- package/src/scripts/hooks/chain_nudge_hook.ts +311 -25
- package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +107 -20
- package/src/scripts/hooks/session_eol_hook.ts +177 -145
- package/src/scripts/hooks/source_first_gate_hook.ts +12 -0
- package/src/scripts/hooks/ui_route_nudge_hook.ts +52 -15
- package/src/scripts/hot_context_hook.ts +103 -408
- package/src/scripts/lint_evidence_artifacts.ts +1 -0
- package/src/scripts/lint_knowledge_scale.ts +0 -26
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +3 -13
- package/src/scripts/memory_lookup.ts +9 -9
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +86 -8
- package/src/scripts/sync_agent_settings.ts +270 -10
- package/src/server/io/yamlIO.ts +127 -17
- package/src/server/schemas/settings.ts +45 -6
- package/src/shared/settingsSurface.ts +13 -2
- package/src/templates/compact-instructions.md +8 -3
- package/src/vendor/grammars/README.md +27 -2
- package/dist/agent-src/commands/chat-history/import.md +0 -170
- package/dist/agent-src/commands/chat-history.md +0 -63
|
@@ -20,12 +20,25 @@
|
|
|
20
20
|
* deterministic (real tokenizer, reported by `session_index_cost()`); the
|
|
21
21
|
* hit-rate arm needs a live paired run and has NOT been executed — so the
|
|
22
22
|
* default is off, per the roadmap's "off unless proven".
|
|
23
|
+
*
|
|
24
|
+
* loss_class: recoverable-lossy
|
|
25
|
+
* loss_recovery: agents/memory/ — this module renders the block and applies
|
|
26
|
+
* `capRows`, so it carries the same class as the cap it applies. Each dropped
|
|
27
|
+
* row names a curated entry that remains on disk and is fetchable by id.
|
|
23
28
|
*/
|
|
24
29
|
|
|
25
|
-
import { CURATED_TYPES, retrieve_v1 } from './memory_lookup.js';
|
|
30
|
+
import { CURATED_TYPES, MEMORY_ROOT, retrieve_v1 } from './memory_lookup.js';
|
|
26
31
|
import { load_agent_settings } from './_lib/agent_settings.js';
|
|
32
|
+
import {
|
|
33
|
+
capRows,
|
|
34
|
+
orderRows,
|
|
35
|
+
SESSION_INDEX_ROW_CAP,
|
|
36
|
+
verifyMemoryRoot,
|
|
37
|
+
type TrustGrant,
|
|
38
|
+
type TrustVerdict,
|
|
39
|
+
} from './_lib/session_index_trust.js';
|
|
27
40
|
|
|
28
|
-
export
|
|
41
|
+
export { SESSION_INDEX_ROW_CAP };
|
|
29
42
|
|
|
30
43
|
interface IndexRow {
|
|
31
44
|
id: string;
|
|
@@ -54,21 +67,72 @@ export function session_index_enabled(root: string): boolean {
|
|
|
54
67
|
* Expects the caller to have chdir'd to the workspace root — memory roots
|
|
55
68
|
* are cwd-relative, same contract as the MCP tool handlers.
|
|
56
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* How many entries retrieval is asked for before the declared order picks.
|
|
72
|
+
*
|
|
73
|
+
* R2 finding 2: asking retrieval for the CAP made P4 decorative. Every curated
|
|
74
|
+
* hit scores an identical 0.1 with an empty key set, so `retrieve` sliced the
|
|
75
|
+
* first 30 in STORE order at `memory_lookup.ts:995` and `orderRows` then sorted
|
|
76
|
+
* a set the cap had already chosen. Measured by the reviewer: a 40-entry corpus
|
|
77
|
+
* with the 5 cheapest last emitted zero cheap rows.
|
|
78
|
+
*
|
|
79
|
+
* The number must therefore exceed any realistic curated corpus, not merely the
|
|
80
|
+
* cap. 500 is a STATED DEFAULT, not a measured optimum — the cost of asking for
|
|
81
|
+
* more than exists is bounded by what exists, since `detail: 'index'` returns a
|
|
82
|
+
* row rather than a body. Its falsifier is recorded in the roadmap: a corpus
|
|
83
|
+
* larger than this bound makes store order decide again, silently.
|
|
84
|
+
*/
|
|
85
|
+
export const SESSION_INDEX_RETRIEVAL_LIMIT = 500;
|
|
86
|
+
|
|
57
87
|
export function session_index_rows(cap: number = SESSION_INDEX_ROW_CAP): IndexRow[] {
|
|
58
|
-
|
|
88
|
+
// Retrieval is asked for far more than the cap, so the declared order below
|
|
89
|
+
// is what chooses which rows survive truncation. P4 and P6 are one
|
|
90
|
+
// decision: retrieve wide, order, THEN cap — and never the reverse.
|
|
91
|
+
const envelope = retrieve_v1([...CURATED_TYPES], [], SESSION_INDEX_RETRIEVAL_LIMIT, { detail: 'index' });
|
|
59
92
|
const entries = (envelope['entries'] as Array<Record<string, unknown>>) ?? [];
|
|
60
|
-
|
|
93
|
+
const rows: IndexRow[] = entries.map((e) => ({
|
|
61
94
|
id: String(e['id'] ?? ''),
|
|
62
95
|
title: String(e['title'] ?? ''),
|
|
63
96
|
tokens_estimate: Number(e['tokens_estimate'] ?? 0),
|
|
64
97
|
}));
|
|
98
|
+
return capRows(orderRows(rows), cap);
|
|
65
99
|
}
|
|
66
100
|
|
|
67
101
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
102
|
+
* The trust gate, separated from the render so a caller can log WHY nothing
|
|
103
|
+
* was injected. `null` from {@link build_session_index_block} is ambiguous by
|
|
104
|
+
* construction — empty corpus and refused root look identical — and the
|
|
105
|
+
* distinction is the point of the contract, so it is available here.
|
|
106
|
+
*
|
|
107
|
+
* The workspace root is REQUIRED. `MEMORY_ROOT` is relative, so without a root
|
|
108
|
+
* to resolve it against there is nothing to check identity or containment
|
|
109
|
+
* against, and a default would be a bypass wearing a convenience.
|
|
70
110
|
*/
|
|
71
|
-
export function
|
|
111
|
+
export function session_index_trust(workspaceRoot: string, now?: Date): TrustVerdict {
|
|
112
|
+
// `exactOptionalPropertyTypes` refuses `now: undefined` on an optional
|
|
113
|
+
// field, so the key is omitted rather than passed as undefined.
|
|
114
|
+
return verifyMemoryRoot({
|
|
115
|
+
workspaceRoot,
|
|
116
|
+
relativeMemoryRoot: MEMORY_ROOT,
|
|
117
|
+
...(now === undefined ? {} : { now }),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* RENDER a block from whatever `MEMORY_ROOT` currently resolves to. Checks
|
|
123
|
+
* nothing, serves nothing.
|
|
124
|
+
*
|
|
125
|
+
* Split out on R2 findings 6 and 7. The trust check used to be an OPTIONAL
|
|
126
|
+
* parameter, which made the serving path opt-in — and `session_index_cost()`
|
|
127
|
+
* omitted it, so the one function whose name suggests it only measures was also
|
|
128
|
+
* the one that could render unchecked. Now the two operations have two names:
|
|
129
|
+
* this one renders (for cost measurement and for the unit seam that injects an
|
|
130
|
+
* absolute fixture root via `_setMemoryRoot`, which by construction sits
|
|
131
|
+
* outside any workspace), and {@link serve_session_index_block} is the only way
|
|
132
|
+
* to obtain a block for INJECTION — it takes a granted verdict, so there is no
|
|
133
|
+
* unchecked serving path to forget.
|
|
134
|
+
*/
|
|
135
|
+
export function render_session_index_block(cap: number = SESSION_INDEX_ROW_CAP): string | null {
|
|
72
136
|
const rows = session_index_rows(cap);
|
|
73
137
|
if (rows.length === 0) return null;
|
|
74
138
|
const lines = [
|
|
@@ -81,13 +145,27 @@ export function build_session_index_block(cap: number = SESSION_INDEX_ROW_CAP):
|
|
|
81
145
|
return lines.join('\n');
|
|
82
146
|
}
|
|
83
147
|
|
|
148
|
+
/**
|
|
149
|
+
* The only path to a block that will be INJECTED.
|
|
150
|
+
*
|
|
151
|
+
* Takes a granted verdict rather than a root, so the check happens once, at the
|
|
152
|
+
* caller, and cannot be skipped here — R2 finding 7 was that the hook and the
|
|
153
|
+
* builder each computed it, for a diagnostic the dispatcher does not forward.
|
|
154
|
+
* The type is `TrustGrant`, not `TrustVerdict`: a refusal cannot be passed in,
|
|
155
|
+
* so "did you check?" is answered by the signature instead of by a convention.
|
|
156
|
+
*/
|
|
157
|
+
export function serve_session_index_block(_grant: TrustGrant, cap: number = SESSION_INDEX_ROW_CAP): string | null {
|
|
158
|
+
return render_session_index_block(cap);
|
|
159
|
+
}
|
|
160
|
+
|
|
84
161
|
/**
|
|
85
162
|
* Deterministic fixed cost of the block in real tokens (cl100k_base) —
|
|
86
163
|
* the measurable arm of the roadmap's ship-criterion. Lazy-loads the
|
|
87
164
|
* tokenizer so the enabled=off fast path never pays it.
|
|
88
165
|
*/
|
|
89
166
|
export async function session_index_cost(cap: number = SESSION_INDEX_ROW_CAP): Promise<number> {
|
|
90
|
-
|
|
167
|
+
// Renders rather than serves: this measures a cost, it injects nothing.
|
|
168
|
+
const block = render_session_index_block(cap);
|
|
91
169
|
if (block === null) return 0;
|
|
92
170
|
const { gpt_tokens } = await import('./_lib/token_count.js');
|
|
93
171
|
return gpt_tokens(block).tokens;
|
|
@@ -441,11 +441,201 @@ function loadTemplate(p: string, profileValues: Record<string, string>): string
|
|
|
441
441
|
return renderTemplate(fs.readFileSync(p, 'utf-8'), profileValues);
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
export interface DuplicateRepair {
|
|
445
|
+
/** The document with every safely-collapsible duplicate run reduced to one. */
|
|
446
|
+
text: string;
|
|
447
|
+
/** Keys that were collapsed, in first-seen order. */
|
|
448
|
+
collapsed: string[];
|
|
449
|
+
/** Duplicated keys this pass refuses to touch, with the reason. */
|
|
450
|
+
unsafe: string[];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Collapse duplicate TOP-LEVEL inline-scalar keys, keeping the last value.
|
|
455
|
+
*
|
|
456
|
+
* A strict YAML parser rejects a duplicate mapping key outright, so one such
|
|
457
|
+
* pair takes the whole file — and with it `task sync`, `task release-prepare`
|
|
458
|
+
* and every release — out of service with "Map keys must be unique". The
|
|
459
|
+
* duplicates in the wild were written by `mergeIntoTemplate`, whose flat
|
|
460
|
+
* `a.b: value` append was not idempotent before this change: each wizard save
|
|
461
|
+
* re-appended the entire "Wizard-added keys" block. That writer is fixed, but
|
|
462
|
+
* every file it already corrupted stays unreadable until something repairs
|
|
463
|
+
* it, and a release is the worst moment to discover that.
|
|
464
|
+
*
|
|
465
|
+
* Last-wins is value-neutral, not a guess: a lenient reader already resolved
|
|
466
|
+
* such a run that way, so collapsing changes what the file PARSES as in no
|
|
467
|
+
* way — it only changes whether it parses at all.
|
|
468
|
+
*
|
|
469
|
+
* Deliberately narrow, and the narrowness is what makes last-wins safe. A key
|
|
470
|
+
* is collapsible only when EVERY occurrence in the run is ONE-LINE-ONLY — its
|
|
471
|
+
* value sits on the key's own line and it owns no continuation lines at all.
|
|
472
|
+
* Anything else is reported in `unsafe` and left alone, because dropping the
|
|
473
|
+
* loser of a multi-line pair does not drop its body: the body survives, stops
|
|
474
|
+
* being that key's value, and attaches to whatever line precedes it. That
|
|
475
|
+
* turns a parse error into silent data corruption, which is strictly worse.
|
|
476
|
+
*
|
|
477
|
+
* Refused for that reason: a block key with indented children, a block scalar
|
|
478
|
+
* (`|`, `>`), a flow collection opened on one line and closed on another, and
|
|
479
|
+
* an anchor / alias / tag / merge value, whose meaning depends on which
|
|
480
|
+
* occurrence a reader binds rather than only on the value.
|
|
481
|
+
*
|
|
482
|
+
* A multi-document stream (`---` / `...`) is not repaired either. The reader
|
|
483
|
+
* takes a single document and rejects such a file whatever this pass does, so
|
|
484
|
+
* collapsing across a boundary could only merge two documents' keys into one.
|
|
485
|
+
*/
|
|
486
|
+
export function collapseDuplicateFlatKeys(text: string): DuplicateRepair {
|
|
487
|
+
// Line endings are preserved: splitting on `\n` alone leaves a trailing
|
|
488
|
+
// `\r` on every CRLF line, which the key pattern then never matches — so a
|
|
489
|
+
// CRLF file reported zero duplicates and fell straight into the parse error
|
|
490
|
+
// this pass exists to prevent.
|
|
491
|
+
//
|
|
492
|
+
// The MAJORITY terminator, not "any CRLF present". A mostly-LF file with one
|
|
493
|
+
// stray CRLF line would otherwise be rewritten to CRLF throughout — a whole-
|
|
494
|
+
// file change nobody asked for, on a pass whose entire promise is that it
|
|
495
|
+
// touches only what it must. Matches `sync_yaml_rt.detectEol`.
|
|
496
|
+
const crlfCount = (text.match(/\r\n/g) ?? []).length;
|
|
497
|
+
const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
|
|
498
|
+
const eol = crlfCount > lfCount ? '\r\n' : '\n';
|
|
499
|
+
const lines = text.split(/\r?\n/);
|
|
500
|
+
|
|
501
|
+
// A document separator anywhere means more than one document may be in play.
|
|
502
|
+
if (lines.some((l) => /^(---|\.\.\.)\s*$/.test(l))) {
|
|
503
|
+
return { text, collapsed: [], unsafe: [] };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// A mapping key ends at a colon FOLLOWED BY SPACE OR END OF LINE — YAML's
|
|
507
|
+
// own plain-scalar rule. Stopping at the first colon instead reads `a:b: 1`
|
|
508
|
+
// as the key `a`, so a file carrying both `a:b:` and `a:` saw two `a` heads
|
|
509
|
+
// and collapsed them: the `a:b` entry vanished and the result parsed
|
|
510
|
+
// cleanly, so nothing downstream could notice. `sync_yaml_rt`'s own
|
|
511
|
+
// tokeniser already had this right; the two disagreeing was the defect.
|
|
512
|
+
//
|
|
513
|
+
// The value is captured with `[\s\S]` rather than `.`, which does not match
|
|
514
|
+
// a lone `\r`. A line holding one (a value like `"x\ry"`) was not recognised
|
|
515
|
+
// as a head at all, so a duplicate of it stayed invisible and the parse
|
|
516
|
+
// error this pass exists to remove survived untouched.
|
|
517
|
+
const HEAD_RE = /^([A-Za-z_][A-Za-z0-9_.-]*)[ \t]*:(?=[ \t]|$)([\s\S]*)$/;
|
|
518
|
+
|
|
519
|
+
/** Top-level key lines, in order. */
|
|
520
|
+
const heads: { key: string; at: number; rest: string }[] = [];
|
|
521
|
+
for (let i = 0; i < lines.length; i++) {
|
|
522
|
+
const line = lines[i];
|
|
523
|
+
if (line === undefined) continue;
|
|
524
|
+
if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
|
|
525
|
+
// Top level only — an indented `a.b:` is somebody else's child key.
|
|
526
|
+
if (line.length !== line.trimStart().length) continue;
|
|
527
|
+
const m = HEAD_RE.exec(line);
|
|
528
|
+
if (m === null) continue;
|
|
529
|
+
heads.push({ key: m[1] as string, at: i, rest: (m[2] as string).trim() });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Why this key may NOT be collapsed, or null when it may.
|
|
534
|
+
*
|
|
535
|
+
* Collapsing is safe only for a key that occupies exactly its own line: its
|
|
536
|
+
* value is a plain inline scalar and nothing between here and the next
|
|
537
|
+
* top-level key belongs to it. Everything else keeps its real reason, so the
|
|
538
|
+
* refusal message names what actually blocked it rather than asserting
|
|
539
|
+
* "different children" over a null, an anchor or a block scalar.
|
|
540
|
+
*/
|
|
541
|
+
const blocker = (h: { at: number; rest: string }, nextAt: number): string | null => {
|
|
542
|
+
const v = h.rest;
|
|
543
|
+
if (v === '' || v.startsWith('#')) return 'a block key or a null value';
|
|
544
|
+
if (/^[|>]/.test(v)) return 'a block scalar';
|
|
545
|
+
// An anchor, alias or tag ANYWHERE in the value, not merely at its start:
|
|
546
|
+
// `a: [&x 1]` fits on one line, and dropping it deletes an anchor that an
|
|
547
|
+
// alias on some other line binds to — turning a duplicate-key error into
|
|
548
|
+
// an unresolved-alias error. Token position only, so `a&b` in a plain
|
|
549
|
+
// scalar is left alone.
|
|
550
|
+
if (/(^|[\s[{,])[&*!]/.test(v)) return 'an anchor, alias or tag';
|
|
551
|
+
// A flow collection must open and close on this line.
|
|
552
|
+
const opens = (v.match(/[[{]/g) ?? []).length;
|
|
553
|
+
const closes = (v.match(/[\]}]/g) ?? []).length;
|
|
554
|
+
if (opens !== closes) return 'a flow collection spanning lines';
|
|
555
|
+
for (let i = h.at + 1; i < nextAt; i++) {
|
|
556
|
+
const line = lines[i];
|
|
557
|
+
if (line === undefined) continue;
|
|
558
|
+
if (line.trim() === '') continue;
|
|
559
|
+
if (line.trimStart().startsWith('#')) continue;
|
|
560
|
+
return 'a value spanning more than its own line';
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const solo = new Set<number>();
|
|
566
|
+
const why = new Map<number, string>();
|
|
567
|
+
for (let n = 0; n < heads.length; n++) {
|
|
568
|
+
const h = heads[n] as { key: string; at: number; rest: string };
|
|
569
|
+
const nextAt = n + 1 < heads.length ? (heads[n + 1] as { at: number }).at : lines.length;
|
|
570
|
+
const reason = blocker(h, nextAt);
|
|
571
|
+
if (reason === null) solo.add(h.at);
|
|
572
|
+
else why.set(h.at, reason);
|
|
447
573
|
}
|
|
448
|
-
|
|
574
|
+
|
|
575
|
+
const seen = new Map<string, number[]>();
|
|
576
|
+
for (const h of heads) {
|
|
577
|
+
const at = seen.get(h.key);
|
|
578
|
+
if (at === undefined) seen.set(h.key, [h.at]);
|
|
579
|
+
else at.push(h.at);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const collapsed: string[] = [];
|
|
583
|
+
const unsafe: string[] = [];
|
|
584
|
+
const drop = new Set<number>();
|
|
585
|
+
for (const [key, hits] of seen) {
|
|
586
|
+
if (hits.length < 2) continue;
|
|
587
|
+
if (!hits.every((i) => solo.has(i))) {
|
|
588
|
+
const reason = hits.map((i) => why.get(i)).find((r) => r !== undefined) ?? 'not a plain one-line value';
|
|
589
|
+
unsafe.push(`${key} — ${reason}`);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
collapsed.push(key);
|
|
593
|
+
// Keep the LAST occurrence's value at the FIRST occurrence's position, so
|
|
594
|
+
// the surrounding comments stay attached to the line they document.
|
|
595
|
+
lines[hits[0] as number] = lines[hits[hits.length - 1] as number] as string;
|
|
596
|
+
for (let i = 1; i < hits.length; i++) drop.add(hits[i] as number);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Duplicates this pass cannot even SEE, reported rather than skipped.
|
|
600
|
+
//
|
|
601
|
+
// `HEAD_RE` recognises the key shapes the repair understands. A key starting
|
|
602
|
+
// with a digit (`2fa.on`) or a quoted key (`"a:b"`) matches nothing, so a
|
|
603
|
+
// file duplicating one used to come back with zero collapses AND zero
|
|
604
|
+
// refusals — and then died on the strict parse with the same opaque "Map keys
|
|
605
|
+
// must be unique" the whole repair exists to remove, giving the operator no
|
|
606
|
+
// hint that their key was the part being skipped. Shipping a repair pass
|
|
607
|
+
// creates the expectation that it either fixes the file or says why it
|
|
608
|
+
// cannot; silence is neither.
|
|
609
|
+
//
|
|
610
|
+
// Detection only. These keys are never collapsed — the shapes are exactly the
|
|
611
|
+
// ones whose tokenisation this pass does not model, which is why it declines
|
|
612
|
+
// to rewrite them.
|
|
613
|
+
const loose = new Map<string, number>();
|
|
614
|
+
for (let i = 0; i < lines.length; i++) {
|
|
615
|
+
const line = lines[i];
|
|
616
|
+
if (line === undefined) continue;
|
|
617
|
+
if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
|
|
618
|
+
if (line.length !== line.trimStart().length) continue;
|
|
619
|
+
if (HEAD_RE.test(line)) continue;
|
|
620
|
+
const m = /^("[^"]*"|'[^']*'|[^\s:#][^:]*?)[ \t]*:(?=[ \t]|$)/.exec(line);
|
|
621
|
+
if (m === null) continue;
|
|
622
|
+
const key = m[1] as string;
|
|
623
|
+
loose.set(key, (loose.get(key) ?? 0) + 1);
|
|
624
|
+
}
|
|
625
|
+
for (const [key, count] of loose) {
|
|
626
|
+
if (count < 2) continue;
|
|
627
|
+
unsafe.push(`${key} — a key shape this repair does not model (quoted, or not starting with a letter)`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (collapsed.length === 0) {
|
|
631
|
+
return { text, collapsed, unsafe };
|
|
632
|
+
}
|
|
633
|
+
const kept = lines.filter((_, i) => !drop.has(i));
|
|
634
|
+
return { text: kept.join(eol), collapsed, unsafe };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function loadUserText(raw: string): Record<string, unknown> {
|
|
638
|
+
const data = parseYaml(raw, { version: '1.1' });
|
|
449
639
|
if (data === null || data === undefined) {
|
|
450
640
|
return {};
|
|
451
641
|
}
|
|
@@ -655,10 +845,61 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
655
845
|
const templatePath = args.template;
|
|
656
846
|
const profileDir = args.profile_dir;
|
|
657
847
|
|
|
848
|
+
// Repair BEFORE the first parse. A duplicate mapping key makes the whole
|
|
849
|
+
// file unreadable, so without this the run dies here and takes the release
|
|
850
|
+
// with it — see `collapseDuplicateFlatKeys` for why last-wins is safe.
|
|
851
|
+
const rawText = isFile(target) ? fs.readFileSync(target, 'utf-8') : '';
|
|
852
|
+
const repair = collapseDuplicateFlatKeys(rawText);
|
|
853
|
+
if (repair.unsafe.length > 0) {
|
|
854
|
+
// Each entry carries the reason that key was refused. A single blanket
|
|
855
|
+
// sentence used to claim "different children" for every refusal — over a
|
|
856
|
+
// null value, a block scalar or an anchor that has none — which sends the
|
|
857
|
+
// reader looking for children to merge that are not there.
|
|
858
|
+
process.stderr.write(
|
|
859
|
+
`error: ${target} has duplicate mapping keys this tool will not collapse:\n` +
|
|
860
|
+
repair.unsafe.map((u) => ` - ${u}\n`).join('') +
|
|
861
|
+
`Keeping one occurrence would change what the file means. Merge them by hand, then re-run.\n`,
|
|
862
|
+
);
|
|
863
|
+
return 2;
|
|
864
|
+
}
|
|
865
|
+
if (repair.collapsed.length > 0) {
|
|
866
|
+
// NOT gated on --quiet, and not on stdout. Both callers that matter run
|
|
867
|
+
// quiet — the release's `sync-agent-settings` step and the pre-release
|
|
868
|
+
// probe — so gating this would let a release rewrite the operator's own
|
|
869
|
+
// settings file with no trace. `--quiet` suppresses routine success
|
|
870
|
+
// chatter; "I modified your file" is not that.
|
|
871
|
+
//
|
|
872
|
+
// The tense follows the mode. `--check` and `--dry-run` never write, so
|
|
873
|
+
// announcing a completed collapse there describes a write that did not
|
|
874
|
+
// happen — and the pre-release probe runs `--dry-run`, which made that the
|
|
875
|
+
// FIRST thing an operator saw on every release with a corrupted file.
|
|
876
|
+
const writes = !args.check && !args.dry_run;
|
|
877
|
+
const verb = writes
|
|
878
|
+
? `collapsed ${repair.collapsed.length} duplicate key(s), last value kept`
|
|
879
|
+
: `would collapse ${repair.collapsed.length} duplicate key(s), keeping the last value (no write in this mode)`;
|
|
880
|
+
process.stderr.write(`🔧 ${target}: ${verb} — ${repair.collapsed.join(', ')}\n`);
|
|
881
|
+
|
|
882
|
+
// The collapse promises to change only WHETHER the file parses, never what
|
|
883
|
+
// it parses as — and that promise is only meaningful over an input that had
|
|
884
|
+
// a reading to preserve. If the original was unparseable for some further
|
|
885
|
+
// reason (an unterminated quote, say), the repaired file may parse to
|
|
886
|
+
// structure no human wrote, and the next `git diff` shows keys nobody
|
|
887
|
+
// added. Cheap to say, and the operator cannot recover it from the output.
|
|
888
|
+
try {
|
|
889
|
+
parseYaml(rawText, { version: '1.1', uniqueKeys: false });
|
|
890
|
+
} catch {
|
|
891
|
+
process.stderr.write(
|
|
892
|
+
`⚠️ ${target}: the original had a syntax error beyond the duplicate keys, so the ` +
|
|
893
|
+
`result may differ in structure, not only in duplicates — read the diff before trusting it.\n`,
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const sourceText = repair.text;
|
|
898
|
+
|
|
658
899
|
let profile: string;
|
|
659
900
|
let templateBody: string;
|
|
660
901
|
try {
|
|
661
|
-
const userData =
|
|
902
|
+
const userData = loadUserText(sourceText);
|
|
662
903
|
const personalRaw = userData['personal'];
|
|
663
904
|
const personal =
|
|
664
905
|
personalRaw !== null && typeof personalRaw === 'object' && !Array.isArray(personalRaw)
|
|
@@ -706,7 +947,12 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
706
947
|
throw err;
|
|
707
948
|
}
|
|
708
949
|
|
|
709
|
-
|
|
950
|
+
// The merge reads the REPAIRED text; every "did anything change" decision
|
|
951
|
+
// below compares against `rawText`, what is actually on disk. Comparing
|
|
952
|
+
// against the repaired text instead would report "already in sync" on a
|
|
953
|
+
// file whose collapse never reached disk — the repair would run on every
|
|
954
|
+
// invocation and fix nothing.
|
|
955
|
+
const existingText = sourceText;
|
|
710
956
|
|
|
711
957
|
let newText: string;
|
|
712
958
|
if (existingText) {
|
|
@@ -724,7 +970,7 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
724
970
|
newText = templateBody;
|
|
725
971
|
}
|
|
726
972
|
|
|
727
|
-
if (newText ===
|
|
973
|
+
if (newText === rawText) {
|
|
728
974
|
if (!args.quiet) {
|
|
729
975
|
process.stdout.write(`✅ ${target}: already in sync (profile=${profile})\n`);
|
|
730
976
|
}
|
|
@@ -732,14 +978,28 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
732
978
|
}
|
|
733
979
|
|
|
734
980
|
if (args.check) {
|
|
735
|
-
const diff = renderDiff(
|
|
981
|
+
const diff = renderDiff(rawText, newText, String(target));
|
|
736
982
|
process.stdout.write(diff);
|
|
737
|
-
|
|
983
|
+
// Name WHICH problem, because the two have different remedies and a CI job
|
|
984
|
+
// reading only "drift detected" cannot tell them apart. A repair-only diff
|
|
985
|
+
// means the file is corrupt, not out of step with the template; the
|
|
986
|
+
// discriminator is whether the merge changed anything beyond the collapse.
|
|
987
|
+
//
|
|
988
|
+
// Still exit 2 in both cases: the file needs a write either way, and a
|
|
989
|
+
// `--check` that returned 0 over a file the reader cannot parse would hide
|
|
990
|
+
// exactly the breakage this whole change exists to surface.
|
|
991
|
+
const repairOnly = repair.collapsed.length > 0 && newText === sourceText;
|
|
992
|
+
process.stderr.write(
|
|
993
|
+
repairOnly
|
|
994
|
+
? `\n❌ ${target}: duplicate keys need collapsing — no template drift (profile=${profile})\n` +
|
|
995
|
+
` Run \`sync_agent_settings\` without --check to apply the repair.\n`
|
|
996
|
+
: `\n❌ ${target}: drift detected (profile=${profile})\n`,
|
|
997
|
+
);
|
|
738
998
|
return 2;
|
|
739
999
|
}
|
|
740
1000
|
|
|
741
1001
|
if (args.dry_run) {
|
|
742
|
-
const diff = renderDiff(
|
|
1002
|
+
const diff = renderDiff(rawText, newText, String(target));
|
|
743
1003
|
process.stdout.write(diff);
|
|
744
1004
|
if (!args.quiet) {
|
|
745
1005
|
process.stderr.write(`\n(dry-run) would update ${target} (profile=${profile})\n`);
|
package/src/server/io/yamlIO.ts
CHANGED
|
@@ -40,6 +40,25 @@ function formatScalar(value: unknown): string {
|
|
|
40
40
|
return yamlDump(value, { lineWidth: -1, flowLevel: 0 }).replace(/\n$/, '').trim();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* A NESTED mapping key, as both the presence probe and the insertion walk read
|
|
45
|
+
* one. Shared so the writer and its own existence-probe cannot drift apart —
|
|
46
|
+
* that drift is the shape this file has now hit four times.
|
|
47
|
+
*
|
|
48
|
+
* `-` is in the class because a dashed key is ordinary YAML (`first-name`,
|
|
49
|
+
* `dry-run`, `retry-count`) and `detectIndentWidth` already accepted one.
|
|
50
|
+
* While this pattern did not, `findScalarLine` could never find such a key:
|
|
51
|
+
* `mergeIntoTemplate` then wrote a bogus top-level `x.first-principles:` line
|
|
52
|
+
* beside the real nested one and the intended value was never written — a save
|
|
53
|
+
* that silently did nothing — and `upsertScalar` appended another child line on
|
|
54
|
+
* EVERY call, growing without bound until the file stopped parsing.
|
|
55
|
+
*
|
|
56
|
+
* `.` stays OUT, deliberately. A dotted key is the FLAT form, which
|
|
57
|
+
* `replaceFlatDottedKey` owns; letting this pattern match `a.b:` would make the
|
|
58
|
+
* nested probe claim a line that is one key literally named "a.b".
|
|
59
|
+
*/
|
|
60
|
+
const NESTED_KEY_RE = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:/;
|
|
61
|
+
|
|
43
62
|
interface FlatEntry {
|
|
44
63
|
path: string[];
|
|
45
64
|
value: unknown;
|
|
@@ -68,6 +87,25 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
|
|
|
68
87
|
const key = dottedPath[dottedPath.length - 1];
|
|
69
88
|
|
|
70
89
|
const lines = template.split('\n');
|
|
90
|
+
const at = findScalarLine(lines, sections, key);
|
|
91
|
+
if (at === -1) return template;
|
|
92
|
+
const width = detectIndentWidth(lines);
|
|
93
|
+
lines[at] = `${' '.repeat(width * sections.length)}${key}: ${formatScalar(value)}`;
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Index of the line holding the scalar at `sections`/`key`, or -1.
|
|
99
|
+
*
|
|
100
|
+
* Split out of `replaceScalar` so that "is this path present?" is answerable
|
|
101
|
+
* WITHOUT writing. `mergeIntoTemplate` used to infer presence from
|
|
102
|
+
* `replaceScalar` returning a changed string, which conflates "absent" with
|
|
103
|
+
* "present and already equal to the value being written" — under that test a
|
|
104
|
+
* no-op write reads as a miss and the caller appends a duplicate mapping key.
|
|
105
|
+
* A presence question answered by a mutation's side effect is the shape to
|
|
106
|
+
* look for here; the file already carries two earlier rounds of it.
|
|
107
|
+
*/
|
|
108
|
+
function findScalarLine(lines: readonly string[], sections: readonly string[], key: string | undefined): number {
|
|
71
109
|
// The document's own width, the same read `upsertScalar` performs.
|
|
72
110
|
//
|
|
73
111
|
// R2 round 5, finding 1, and it is the second half of round 4's finding 7.
|
|
@@ -84,9 +122,7 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
|
|
|
84
122
|
// A writer and its own existence-probe disagreeing about the format is the
|
|
85
123
|
// shape to look for whenever one of a pair is taught something new.
|
|
86
124
|
const width = detectIndentWidth(lines);
|
|
87
|
-
const targetIndent = ' '.repeat(width * sections.length);
|
|
88
125
|
const currentPath: (string | null)[] = new Array<string | null>(sections.length).fill(null);
|
|
89
|
-
const formatted = formatScalar(value);
|
|
90
126
|
|
|
91
127
|
for (let i = 0; i < lines.length; i++) {
|
|
92
128
|
const line = lines[i];
|
|
@@ -98,7 +134,7 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
|
|
|
98
134
|
const level = indentLen / width;
|
|
99
135
|
if (level > sections.length) continue;
|
|
100
136
|
|
|
101
|
-
const m =
|
|
137
|
+
const m = NESTED_KEY_RE.exec(stripped);
|
|
102
138
|
if (!m) continue;
|
|
103
139
|
const lineKey = m[1];
|
|
104
140
|
if (lineKey === undefined) continue;
|
|
@@ -115,10 +151,9 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
|
|
|
115
151
|
const parentsMatch = sections.every((s, idx) => currentPath[idx] === s);
|
|
116
152
|
if (!parentsMatch) continue;
|
|
117
153
|
if (lineKey !== key) continue;
|
|
118
|
-
|
|
119
|
-
return lines.join('\n');
|
|
154
|
+
return i;
|
|
120
155
|
}
|
|
121
|
-
return
|
|
156
|
+
return -1;
|
|
122
157
|
}
|
|
123
158
|
|
|
124
159
|
/**
|
|
@@ -182,11 +217,21 @@ export function detectIndentWidth(lines: readonly string[]): number {
|
|
|
182
217
|
*/
|
|
183
218
|
export function upsertScalar(template: string, dottedPath: string[], value: unknown): string {
|
|
184
219
|
if (dottedPath.length === 0) return template;
|
|
185
|
-
const replaced = replaceScalar(template, dottedPath, value);
|
|
186
|
-
if (replaced !== template) return replaced;
|
|
187
|
-
|
|
188
220
|
const sections = dottedPath.slice(0, -1);
|
|
189
221
|
const key = dottedPath[dottedPath.length - 1] as string;
|
|
222
|
+
|
|
223
|
+
// Presence is asked, never inferred from the write changing the text. The
|
|
224
|
+
// string compare that used to stand here read "key already holds this
|
|
225
|
+
// value" as "key absent" and fell through to the append below — so saving
|
|
226
|
+
// the wizard's council page twice WITHOUT changing a toggle wrote a second
|
|
227
|
+
// `api_on_quota:` into the same block, over two `{ok: true}` responses, and
|
|
228
|
+
// left `.ai-council.yml` rejected by a strict parser. Third instance of one
|
|
229
|
+
// shape in this file; `findScalarLine` exists to end it, and a fix that
|
|
230
|
+
// taught only `mergeIntoTemplate` would have left the live path here.
|
|
231
|
+
if (findScalarLine(template.split('\n'), sections, key) !== -1) {
|
|
232
|
+
return replaceScalar(template, dottedPath, value);
|
|
233
|
+
}
|
|
234
|
+
|
|
190
235
|
const formatted = formatScalar(value);
|
|
191
236
|
const lines = template.split('\n');
|
|
192
237
|
// The document's OWN indent width, not an assumed two.
|
|
@@ -228,7 +273,7 @@ export function upsertScalar(template: string, dottedPath: string[], value: unkn
|
|
|
228
273
|
if (line.trim() === '' || line.trim().startsWith('#')) continue;
|
|
229
274
|
const indentLen = line.length - line.trimStart().length;
|
|
230
275
|
if (indentLen !== indent.length) continue;
|
|
231
|
-
const m =
|
|
276
|
+
const m = NESTED_KEY_RE.exec(line.trim());
|
|
232
277
|
if (m !== null && m[1] === want) {
|
|
233
278
|
found = i;
|
|
234
279
|
break;
|
|
@@ -269,23 +314,88 @@ export function upsertScalar(template: string, dottedPath: string[], value: unkn
|
|
|
269
314
|
return lines.join('\n');
|
|
270
315
|
}
|
|
271
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Rewrite an existing top-level `a.b.c: value` line — the FLAT form
|
|
319
|
+
* `mergeIntoTemplate` itself appends when a path has no template entry.
|
|
320
|
+
* Returns the template unchanged when no such line exists.
|
|
321
|
+
*
|
|
322
|
+
* `replaceScalar` cannot see this line: its key pattern is
|
|
323
|
+
* `[A-Za-z_][A-Za-z0-9_]*`, with no dot, so a path this module appended on
|
|
324
|
+
* one pass is invisible to the probe on the next and `mergeIntoTemplate`
|
|
325
|
+
* appends a SECOND copy. Two wizard saves therefore produce a file carrying
|
|
326
|
+
* duplicate mapping keys — a strict parser rejects it outright, which is what
|
|
327
|
+
* wedged `task release`: its `sync` step exits 2 with "Map keys must be
|
|
328
|
+
* unique" before the release does any git work.
|
|
329
|
+
*
|
|
330
|
+
* A pre-existing duplicate run is COLLAPSED, not merely updated: the first
|
|
331
|
+
* occurrence keeps its position and takes the new value, every later one is
|
|
332
|
+
* dropped. Collapsing is value-neutral — a lenient reader already resolved
|
|
333
|
+
* such a run last-wins — and it is the only way a file corrupted by the old
|
|
334
|
+
* behaviour becomes parseable again through the ordinary write path.
|
|
335
|
+
*
|
|
336
|
+
* Deliberately NOT folded into `replaceScalar`: `upsertScalar` calls that
|
|
337
|
+
* probe and documents the flat form as the WRONG shape for a nested key (a
|
|
338
|
+
* reader sees one key literally named "a.b"). Teaching the shared probe to
|
|
339
|
+
* match it would make `upsertScalar` write into that broken line instead of
|
|
340
|
+
* creating the nesting. The flat form is legitimate only here, where this
|
|
341
|
+
* function is the one that wrote it.
|
|
342
|
+
*/
|
|
343
|
+
function replaceFlatDottedKey(
|
|
344
|
+
template: string,
|
|
345
|
+
dottedPath: string[],
|
|
346
|
+
value: unknown,
|
|
347
|
+
): { found: boolean; body: string } {
|
|
348
|
+
if (dottedPath.length < 2) return { found: false, body: template };
|
|
349
|
+
const flat = dottedPath.join('.');
|
|
350
|
+
const lines = template.split('\n');
|
|
351
|
+
const hits: number[] = [];
|
|
352
|
+
for (let i = 0; i < lines.length; i++) {
|
|
353
|
+
const line = lines[i];
|
|
354
|
+
if (line === undefined) continue;
|
|
355
|
+
// Top level only — an indented `a.b:` is somebody else's child key.
|
|
356
|
+
if (line.length !== line.trimStart().length) continue;
|
|
357
|
+
const m = /^([A-Za-z_][A-Za-z0-9_.-]*)\s*:/.exec(line);
|
|
358
|
+
if (m === null || m[1] !== flat) continue;
|
|
359
|
+
hits.push(i);
|
|
360
|
+
}
|
|
361
|
+
if (hits.length === 0) return { found: false, body: template };
|
|
362
|
+
lines[hits[0] as number] = `${flat}: ${formatScalar(value)}`;
|
|
363
|
+
// Drop the later copies back-to-front so the earlier indices stay valid.
|
|
364
|
+
for (let i = hits.length - 1; i >= 1; i--) lines.splice(hits[i] as number, 1);
|
|
365
|
+
return { found: true, body: lines.join('\n') };
|
|
366
|
+
}
|
|
367
|
+
|
|
272
368
|
/**
|
|
273
369
|
* Apply every leaf change from `newValues` to `templateBody`. Paths that
|
|
274
370
|
* are not present in the template are appended at the end. Comments are
|
|
275
371
|
* preserved for every key that already exists in the template.
|
|
372
|
+
*
|
|
373
|
+
* Idempotent: applying the same values twice yields the same document, so a
|
|
374
|
+
* second wizard save re-writes the flat block instead of duplicating it.
|
|
276
375
|
*/
|
|
277
376
|
export function mergeIntoTemplate(templateBody: string, newValues: Record<string, unknown>): string {
|
|
278
377
|
let body = templateBody;
|
|
279
378
|
const appended: string[] = [];
|
|
280
379
|
for (const entry of flatten(newValues)) {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
380
|
+
const sections = entry.path.slice(0, -1);
|
|
381
|
+
const leaf = entry.path[entry.path.length - 1];
|
|
382
|
+
// Presence is asked, never inferred from the write changing the text —
|
|
383
|
+
// see `findScalarLine`. Writing the value it already holds is a hit.
|
|
384
|
+
if (findScalarLine(body.split('\n'), sections, leaf) !== -1) {
|
|
385
|
+
body = replaceScalar(body, entry.path, entry.value);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
// Not in the template's nested form. It may still be here as a flat
|
|
389
|
+
// dotted line this function appended on an earlier pass.
|
|
390
|
+
const flat = replaceFlatDottedKey(body, entry.path, entry.value);
|
|
391
|
+
if (flat.found) {
|
|
392
|
+
body = flat.body;
|
|
393
|
+
continue;
|
|
288
394
|
}
|
|
395
|
+
// Genuinely absent — append a flat key=value at EOF. Form coverage is
|
|
396
|
+
// asserted by parity test, so this branch only fires on hand-edited
|
|
397
|
+
// templates.
|
|
398
|
+
appended.push(`${entry.path.join('.')}: ${formatScalar(entry.value)}`);
|
|
289
399
|
}
|
|
290
400
|
if (appended.length > 0) {
|
|
291
401
|
if (!body.endsWith('\n')) body += '\n';
|