session-orchestrator 3.16.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/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loop-readiness-banner.mjs — #633, #767
|
|
3
|
+
*
|
|
4
|
+
* Mechanical session-start readiness probe with three independent
|
|
5
|
+
* silent-failure detections for `/loop`:
|
|
6
|
+
*
|
|
7
|
+
* 1. No `.claude/loop.md` in the repo AND no host-wide `~/.claude/loop.md`
|
|
8
|
+
* baseline — bare `/loop` falls back to Anthropic's generic maintenance
|
|
9
|
+
* prompt instead of a project-tuned loop body. (#633)
|
|
10
|
+
* 2. `CLAUDE_CODE_DISABLE_CRON` set to a non-empty value — the cron
|
|
11
|
+
* scheduler backing `/loop` is disabled outright, independent of
|
|
12
|
+
* whether any loop.md file exists. (#767)
|
|
13
|
+
* 3. A present `.claude/loop.md` (repo) or `~/.claude/loop.md` (user)
|
|
14
|
+
* exceeds `LOOP_MD_MAX_BYTES` — Anthropic silently truncates the loaded
|
|
15
|
+
* body past this size, so the operator-authored tail of the file is
|
|
16
|
+
* never read. Checked independently per file. (#767)
|
|
17
|
+
*
|
|
18
|
+
* Plain-JS — no Zod dependency. Never throws. Returns null (silent no-op)
|
|
19
|
+
* when healthy (no findings) or on bad input. Multiple findings are
|
|
20
|
+
* combined into a SINGLE warn object — never an array, never multiple
|
|
21
|
+
* return values.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors the contract used by other Phase 4 banners
|
|
24
|
+
* (`scripts/lib/vault-staleness-banner.mjs`, `scripts/lib/ci-status-banner.mjs`,
|
|
25
|
+
* `scripts/lib/peer-cards/staleness-banner.mjs`): a single `checkXxx()` entry
|
|
26
|
+
* point that returns `null` or `{ severity, message, ... }`.
|
|
27
|
+
*
|
|
28
|
+
* Cross-references:
|
|
29
|
+
* - `.claude/rules/loop-and-monitor.md` — when to use `/loop` vs Monitor vs Routines.
|
|
30
|
+
* - `templates/_shared/loop.md` — the template body `/bootstrap` copies into a repo.
|
|
31
|
+
* - `skills/session-start/SKILL.md` Phase 4 — banner render site.
|
|
32
|
+
* - Issues #633 (original no-loop.md detection), #767 (DISABLE_CRON + truncation).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { existsSync, statSync } from 'node:fs';
|
|
36
|
+
import { join } from 'node:path';
|
|
37
|
+
import os from 'node:os';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Upstream (Anthropic) silently truncates a loaded loop.md body past this
|
|
41
|
+
* size — the tail of an oversized file is never read, a silent-failure
|
|
42
|
+
* class distinct from the file simply being absent. (#767)
|
|
43
|
+
*/
|
|
44
|
+
const LOOP_MD_MAX_BYTES = 25_000;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Return `filePath`'s byte size, or null on any stat failure. Never throws.
|
|
48
|
+
* @param {string} filePath
|
|
49
|
+
* @returns {number | null}
|
|
50
|
+
*/
|
|
51
|
+
function safeStatSize(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
return statSync(filePath).size;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check loop-readiness and produce a session-start banner.
|
|
61
|
+
*
|
|
62
|
+
* Combines up to three independent findings into a single null-or-warn
|
|
63
|
+
* result:
|
|
64
|
+
* - no loop.md anywhere (repo AND user baseline both absent)
|
|
65
|
+
* - `CLAUDE_CODE_DISABLE_CRON` set to a non-empty value
|
|
66
|
+
* - a present loop.md (repo and/or user, checked independently) exceeding
|
|
67
|
+
* `LOOP_MD_MAX_BYTES`
|
|
68
|
+
*
|
|
69
|
+
* @param {{repoRoot: string, homeDir?: string, env?: Record<string, string|undefined>}} opts
|
|
70
|
+
* - `repoRoot`: REQUIRED absolute path to the repo root.
|
|
71
|
+
* - `homeDir`: defaults to `os.homedir()`; exists ONLY for test injection.
|
|
72
|
+
* - `env`: defaults to `process.env`; exists ONLY for test injection.
|
|
73
|
+
* @returns {null | {severity:'warn', message:string, repoLoopMd:boolean, userLoopMd:boolean, disableCron?:boolean, oversize?:string[]}}
|
|
74
|
+
*/
|
|
75
|
+
export function checkLoopReadiness({ repoRoot, homeDir, env } = {}) {
|
|
76
|
+
try {
|
|
77
|
+
if (!repoRoot || typeof repoRoot !== 'string') return null;
|
|
78
|
+
|
|
79
|
+
const home = typeof homeDir === 'string' && homeDir ? homeDir : os.homedir();
|
|
80
|
+
const activeEnv = env && typeof env === 'object' ? env : process.env;
|
|
81
|
+
|
|
82
|
+
const repoLoopMdPath = join(repoRoot, '.claude', 'loop.md');
|
|
83
|
+
const userLoopMdPath = join(home, '.claude', 'loop.md');
|
|
84
|
+
const repoLoopMd = existsSync(repoLoopMdPath);
|
|
85
|
+
const userLoopMd = existsSync(userLoopMdPath);
|
|
86
|
+
|
|
87
|
+
const findings = [];
|
|
88
|
+
|
|
89
|
+
// Finding 1 (#633): neither loop.md exists — bare /loop uses the
|
|
90
|
+
// generic Anthropic maintenance prompt instead of a project-tuned body.
|
|
91
|
+
if (!repoLoopMd && !userLoopMd) {
|
|
92
|
+
findings.push(
|
|
93
|
+
'no .claude/loop.md (repo) and no ~/.claude/loop.md (user baseline) — ' +
|
|
94
|
+
'bare /loop uses the generic Anthropic maintenance prompt. Copy ' +
|
|
95
|
+
'templates/_shared/loop.md via /bootstrap or add a host-wide ' +
|
|
96
|
+
'~/.claude/loop.md.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Finding 2 (#767): CLAUDE_CODE_DISABLE_CRON set — /loop's cron
|
|
101
|
+
// scheduler is disabled outright, independent of loop.md presence.
|
|
102
|
+
const disableCron = Boolean(activeEnv && activeEnv.CLAUDE_CODE_DISABLE_CRON);
|
|
103
|
+
if (disableCron) {
|
|
104
|
+
findings.push(
|
|
105
|
+
'CLAUDE_CODE_DISABLE_CRON is set — the cron scheduler backing /loop ' +
|
|
106
|
+
'is disabled; scheduled loop bodies will not fire.'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Finding 3 (#767): a present loop.md (repo and/or user, checked
|
|
111
|
+
// independently) exceeds the truncation ceiling.
|
|
112
|
+
const oversizeChecks = [
|
|
113
|
+
{ kind: 'repo', filePath: repoLoopMdPath, exists: repoLoopMd },
|
|
114
|
+
{ kind: 'user', filePath: userLoopMdPath, exists: userLoopMd },
|
|
115
|
+
];
|
|
116
|
+
const oversize = [];
|
|
117
|
+
for (const { kind, filePath, exists } of oversizeChecks) {
|
|
118
|
+
if (!exists) continue;
|
|
119
|
+
const size = safeStatSize(filePath);
|
|
120
|
+
if (typeof size === 'number' && size > LOOP_MD_MAX_BYTES) {
|
|
121
|
+
oversize.push(kind);
|
|
122
|
+
findings.push(
|
|
123
|
+
`${filePath} is ${size} bytes, exceeding the ${LOOP_MD_MAX_BYTES}-byte ` +
|
|
124
|
+
"truncation ceiling — Anthropic silently truncates the loaded loop.md " +
|
|
125
|
+
"body past this size, so the file's tail is never read."
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (findings.length === 0) return null;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
severity: 'warn',
|
|
134
|
+
message: '⚠ loop-readiness: ' + findings.join(' '),
|
|
135
|
+
repoLoopMd,
|
|
136
|
+
userLoopMd,
|
|
137
|
+
...(disableCron ? { disableCron: true } : {}),
|
|
138
|
+
...(oversize.length > 0 ? { oversize } : {}),
|
|
139
|
+
};
|
|
140
|
+
} catch {
|
|
141
|
+
// Defensive catch-all — banner must never throw.
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-banner.mjs — Phase 6.7 "What I Remembered" banner renderer (issue #505).
|
|
3
|
+
*
|
|
4
|
+
* Produces the session-start banner string the caller (session-start Phase 6.7)
|
|
5
|
+
* then prints. Pure-ish: only `readBannerInputs` does I/O; `_formatBanner` and
|
|
6
|
+
* the helpers are deterministic and unit-testable on their own.
|
|
7
|
+
*
|
|
8
|
+
* Behavioural contract (issue #505 AC):
|
|
9
|
+
* - Header: "📚 Loaded from memory"
|
|
10
|
+
* - Top-5 active learnings (subject, confidence to 1 decimal, type)
|
|
11
|
+
* - Stats line: memory files · sessions ever · days since last cleanup
|
|
12
|
+
* - Peer-card excerpts (USER.md / AGENT.md), one line each when present
|
|
13
|
+
* - Fresh-repo fallback: a single line when no learnings and no sessions
|
|
14
|
+
* - Disabled or persistence=off: returns '' (silent no-op for the caller)
|
|
15
|
+
*
|
|
16
|
+
* Truncation rule: any composed visible line longer than 80 chars is truncated
|
|
17
|
+
* with a trailing ellipsis so the rendered length is ≤ 80.
|
|
18
|
+
*
|
|
19
|
+
* No external deps beyond stdlib + existing intra-repo helpers.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
23
|
+
import { existsSync } from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
|
|
26
|
+
import { resolveMemoryDir } from './memory-paths.mjs';
|
|
27
|
+
import { readDreamSignals } from './auto-dream.mjs';
|
|
28
|
+
import { readPeerCards } from './peer-cards/reader.mjs';
|
|
29
|
+
import { surfaceTopN, decayOptsFromConfig } from './learnings/surface.mjs';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Constants
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Max visible width of any single banner line, including bullet/prefix. */
|
|
36
|
+
const MAX_LINE_WIDTH = 80;
|
|
37
|
+
|
|
38
|
+
/** Header rendered above the body (omitted on fresh-repo fallback). */
|
|
39
|
+
const HEADER_LINE = '📚 Loaded from memory';
|
|
40
|
+
|
|
41
|
+
/** Verbatim fresh-repo fallback line (mandated by AC). */
|
|
42
|
+
const FRESH_LINE =
|
|
43
|
+
"📚 Memory: 0 entries yet (first session). I'll start learning from this session forward.";
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Pure helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Truncate a string so its visible length is ≤ MAX_LINE_WIDTH. Appends a
|
|
51
|
+
* single-character horizontal-ellipsis ('…') when truncated. No-ops when the
|
|
52
|
+
* input is already within bounds.
|
|
53
|
+
*
|
|
54
|
+
* Operates on UTF-16 code units via String.length — which is what every
|
|
55
|
+
* terminal renderer Counts anyway for the kinds of latin-script content
|
|
56
|
+
* this banner shows. Multi-codepoint emoji (e.g. the header's 📚) are
|
|
57
|
+
* never inside the truncated portion in normal use, but the function
|
|
58
|
+
* tolerates them by trimming the LAST codepoint of any trailing surrogate
|
|
59
|
+
* pair before appending the ellipsis (defence in depth).
|
|
60
|
+
*
|
|
61
|
+
* @param {string} line
|
|
62
|
+
* @param {number} [width=MAX_LINE_WIDTH]
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function _truncateLine(line, width = MAX_LINE_WIDTH) {
|
|
66
|
+
if (typeof line !== 'string') return '';
|
|
67
|
+
if (line.length <= width) return line;
|
|
68
|
+
let cut = line.slice(0, width - 1);
|
|
69
|
+
// Avoid splitting a surrogate pair (orphaned high-surrogate).
|
|
70
|
+
const lastCode = cut.charCodeAt(cut.length - 1);
|
|
71
|
+
if (lastCode >= 0xd800 && lastCode <= 0xdbff) {
|
|
72
|
+
cut = cut.slice(0, -1);
|
|
73
|
+
}
|
|
74
|
+
return cut + '…';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format a single learning line for the banner.
|
|
79
|
+
*
|
|
80
|
+
* Shape: ` • <subject> (<confidence-1dec>, <type>)`
|
|
81
|
+
* Long subjects are truncated with '…' inside the line so the whole rendered
|
|
82
|
+
* line is ≤ 80 chars.
|
|
83
|
+
*
|
|
84
|
+
* @param {{subject: string, confidence: number, type: string}} learning
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
export function _formatLearningLine({ subject, confidence, type }) {
|
|
88
|
+
const subj = typeof subject === 'string' ? subject : '';
|
|
89
|
+
const conf =
|
|
90
|
+
typeof confidence === 'number' && Number.isFinite(confidence)
|
|
91
|
+
? confidence.toFixed(1)
|
|
92
|
+
: '0.0';
|
|
93
|
+
const ty = typeof type === 'string' && type.length > 0 ? type : 'unknown';
|
|
94
|
+
|
|
95
|
+
// Build naive first; truncate at the line level so the suffix stays intact.
|
|
96
|
+
const naive = ` • ${subj} (${conf}, ${ty})`;
|
|
97
|
+
if (naive.length <= MAX_LINE_WIDTH) return naive;
|
|
98
|
+
|
|
99
|
+
// Trim the subject only — keep the trailing (confidence, type) suffix
|
|
100
|
+
// visible because that's what carries actionable signal.
|
|
101
|
+
const suffix = ` (${conf}, ${ty})`;
|
|
102
|
+
const prefix = ' • ';
|
|
103
|
+
// Budget for subject = MAX - prefix.length - suffix.length - 1 (for '…').
|
|
104
|
+
const budget = MAX_LINE_WIDTH - prefix.length - suffix.length - 1;
|
|
105
|
+
if (budget <= 0) {
|
|
106
|
+
// Suffix alone overshoots — fall back to plain truncation.
|
|
107
|
+
return _truncateLine(naive);
|
|
108
|
+
}
|
|
109
|
+
let trimmed = subj.slice(0, budget);
|
|
110
|
+
// Defensive: avoid orphan high-surrogate at the tail.
|
|
111
|
+
const lastCode = trimmed.charCodeAt(trimmed.length - 1);
|
|
112
|
+
if (lastCode >= 0xd800 && lastCode <= 0xdbff) {
|
|
113
|
+
trimmed = trimmed.slice(0, -1);
|
|
114
|
+
}
|
|
115
|
+
return `${prefix}${trimmed}…${suffix}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Format the stats line. Renders "last cleanup: never" when daysSinceCleanup
|
|
120
|
+
* is null; otherwise renders "last cleanup <K> days ago".
|
|
121
|
+
*
|
|
122
|
+
* @param {{memoryFiles: number, sessionsEver: number, daysSinceCleanup: number|null}} stats
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export function _formatStatsLine({ memoryFiles, sessionsEver, daysSinceCleanup }) {
|
|
126
|
+
const mf = Number.isFinite(memoryFiles) ? memoryFiles : 0;
|
|
127
|
+
const se = Number.isFinite(sessionsEver) ? sessionsEver : 0;
|
|
128
|
+
const cleanup =
|
|
129
|
+
daysSinceCleanup === null || daysSinceCleanup === undefined
|
|
130
|
+
? 'last cleanup: never'
|
|
131
|
+
: `last cleanup ${daysSinceCleanup} days ago`;
|
|
132
|
+
const line = `${mf} memory files · ${se} sessions ever · ${cleanup}`;
|
|
133
|
+
return _truncateLine(line);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract a short excerpt from a peer-card body. Returns [sectionHeader, firstContentLine].
|
|
138
|
+
*
|
|
139
|
+
* - sectionHeader: text of the first `## <text>` line (without the `## ` prefix), or null.
|
|
140
|
+
* - firstContentLine: first non-blank line AFTER the section header (or the first
|
|
141
|
+
* non-blank line at all when no `##` header exists). Leading whitespace stripped.
|
|
142
|
+
*
|
|
143
|
+
* Returns [null, null] when the body is empty or whitespace-only.
|
|
144
|
+
*
|
|
145
|
+
* @param {string|null|undefined} cardBody
|
|
146
|
+
* @returns {[string|null, string|null]}
|
|
147
|
+
*/
|
|
148
|
+
export function _extractCardExcerpt(cardBody) {
|
|
149
|
+
if (typeof cardBody !== 'string' || cardBody.length === 0) return [null, null];
|
|
150
|
+
|
|
151
|
+
const lines = cardBody.split('\n');
|
|
152
|
+
const isBlank = (l) => /^\s*$/.test(l);
|
|
153
|
+
|
|
154
|
+
let headerIdx = -1;
|
|
155
|
+
let header = null;
|
|
156
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
157
|
+
const line = lines[i];
|
|
158
|
+
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
159
|
+
if (m) {
|
|
160
|
+
header = m[1];
|
|
161
|
+
headerIdx = i;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Search for the first non-blank line AFTER the header; if no header, scan
|
|
167
|
+
// from the beginning. Skip lines that are themselves `##` headers (a later
|
|
168
|
+
// section header is not "content").
|
|
169
|
+
const startIdx = headerIdx >= 0 ? headerIdx + 1 : 0;
|
|
170
|
+
let content = null;
|
|
171
|
+
for (let i = startIdx; i < lines.length; i += 1) {
|
|
172
|
+
const raw = lines[i];
|
|
173
|
+
if (isBlank(raw)) continue;
|
|
174
|
+
// Skip another header line — we want content text, not the next heading.
|
|
175
|
+
if (/^##\s+/.test(raw)) continue;
|
|
176
|
+
content = raw.replace(/^\s+/, '');
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (header === null && content === null) return [null, null];
|
|
181
|
+
return [header, content];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Format a peer-card excerpt line. Returns null when the excerpt is empty.
|
|
186
|
+
*
|
|
187
|
+
* @param {'USER.md'|'AGENT.md'} label
|
|
188
|
+
* @param {[string|null, string|null]} excerpt
|
|
189
|
+
* @returns {string|null}
|
|
190
|
+
*/
|
|
191
|
+
function formatPeerExcerptLine(label, excerpt) {
|
|
192
|
+
if (!Array.isArray(excerpt)) return null;
|
|
193
|
+
const [section, content] = excerpt;
|
|
194
|
+
if (!section && !content) return null;
|
|
195
|
+
|
|
196
|
+
// When section is missing, fall back to a label-only prefix; when content
|
|
197
|
+
// is missing, drop the trailing colon-content portion.
|
|
198
|
+
let line;
|
|
199
|
+
if (section && content) {
|
|
200
|
+
line = ` ${label} — ${section}: ${content}`;
|
|
201
|
+
} else if (section && !content) {
|
|
202
|
+
line = ` ${label} — ${section}`;
|
|
203
|
+
} else {
|
|
204
|
+
// !section && content
|
|
205
|
+
line = ` ${label} — ${content}`;
|
|
206
|
+
}
|
|
207
|
+
return _truncateLine(line);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// Pure formatter
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Assemble the banner string from pre-cleaned inputs. No I/O, no JSON
|
|
216
|
+
* stringification — every value passed in is already a scalar or a
|
|
217
|
+
* pre-formatted excerpt tuple.
|
|
218
|
+
*
|
|
219
|
+
* Fresh-repo fallback: when `inputs.fresh === true`, the function returns
|
|
220
|
+
* the AC-mandated single line EXACTLY (no header, no stats, no peers).
|
|
221
|
+
*
|
|
222
|
+
* @param {object} inputs
|
|
223
|
+
* @param {Array<{subject:string, confidence:number, type:string}>} inputs.topLearnings
|
|
224
|
+
* @param {{memoryFiles:number, sessionsEver:number, daysSinceCleanup:number|null}|null} inputs.stats
|
|
225
|
+
* @param {{user:[string|null,string|null]|null, agent:[string|null,string|null]|null}} inputs.peerExcerpts
|
|
226
|
+
* @param {boolean} inputs.fresh
|
|
227
|
+
* @returns {string}
|
|
228
|
+
*/
|
|
229
|
+
export function _formatBanner(inputs) {
|
|
230
|
+
if (!inputs || typeof inputs !== 'object') return '';
|
|
231
|
+
|
|
232
|
+
if (inputs.fresh === true) {
|
|
233
|
+
return FRESH_LINE;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const lines = [HEADER_LINE];
|
|
237
|
+
|
|
238
|
+
const topLearnings = Array.isArray(inputs.topLearnings) ? inputs.topLearnings : [];
|
|
239
|
+
for (const learning of topLearnings) {
|
|
240
|
+
if (!learning || typeof learning !== 'object') continue;
|
|
241
|
+
lines.push(_formatLearningLine(learning));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (inputs.stats && typeof inputs.stats === 'object') {
|
|
245
|
+
lines.push(_formatStatsLine(inputs.stats));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const peers = inputs.peerExcerpts ?? {};
|
|
249
|
+
const userLine = formatPeerExcerptLine('USER.md', peers.user);
|
|
250
|
+
if (userLine) lines.push(userLine);
|
|
251
|
+
const agentLine = formatPeerExcerptLine('AGENT.md', peers.agent);
|
|
252
|
+
if (agentLine) lines.push(agentLine);
|
|
253
|
+
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// I/O — readBannerInputs
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Count lines (newline-terminated entries) in a JSONL file. Missing files
|
|
263
|
+
* resolve to 0; read errors resolve to 0. Empty trailing lines are not
|
|
264
|
+
* counted, matching the JSONL convention.
|
|
265
|
+
*
|
|
266
|
+
* @param {string} filePath
|
|
267
|
+
* @returns {Promise<number>}
|
|
268
|
+
*/
|
|
269
|
+
async function countJsonlLines(filePath) {
|
|
270
|
+
if (!existsSync(filePath)) return 0;
|
|
271
|
+
try {
|
|
272
|
+
const raw = await readFile(filePath, 'utf8');
|
|
273
|
+
if (raw.length === 0) return 0;
|
|
274
|
+
return raw.split('\n').filter((l) => l.length > 0).length;
|
|
275
|
+
} catch {
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Count `*.md` files in a directory (non-recursive). Missing directory or
|
|
282
|
+
* read errors resolve to 0. MEMORY.md is INCLUDED in the count — the stats
|
|
283
|
+
* line uses "memory files" as a count of all markdown notes in the memory
|
|
284
|
+
* dir, per the AC.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} dir
|
|
287
|
+
* @returns {Promise<number>}
|
|
288
|
+
*/
|
|
289
|
+
async function countMemoryFiles(dir) {
|
|
290
|
+
if (!existsSync(dir)) return 0;
|
|
291
|
+
try {
|
|
292
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
293
|
+
let count = 0;
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
if (!entry.isFile()) continue;
|
|
296
|
+
if (entry.name.toLowerCase().endsWith('.md')) count += 1;
|
|
297
|
+
}
|
|
298
|
+
return count;
|
|
299
|
+
} catch {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Read every input the banner formatter needs. Pure data — no string
|
|
306
|
+
* formatting. Each subsystem is read defensively: errors degrade to a
|
|
307
|
+
* sensible empty value rather than throwing.
|
|
308
|
+
*
|
|
309
|
+
* @param {object} args
|
|
310
|
+
* @param {string} args.repoRoot — absolute path
|
|
311
|
+
* @param {string} [args.memoryDir] — defaults to `resolveMemoryDir()`
|
|
312
|
+
* @param {string} [args.learningsPath] — defaults to `<repoRoot>/.orchestrator/metrics/learnings.jsonl`
|
|
313
|
+
* @param {Date} [args.now] — injectable clock for tests
|
|
314
|
+
* @param {{enabled?: boolean, 'half-life-days'?: number, 'floor-factor'?: number}} [args.evolveDecay]
|
|
315
|
+
* — the kebab-case `evolve.decay` Session Config block (#670). Bridged to the
|
|
316
|
+
* camelCase `surfaceTopN` `opts.decay` shape via `decayOptsFromConfig`. Absent
|
|
317
|
+
* → DECAY_DEFAULTS apply (conservative 90-day half-life, enabled).
|
|
318
|
+
* @returns {Promise<{
|
|
319
|
+
* topLearnings: Array<{subject:string, confidence:number, type:string}>,
|
|
320
|
+
* stats: {memoryFiles:number, sessionsEver:number, daysSinceCleanup:number|null}|null,
|
|
321
|
+
* peerExcerpts: {user:[string|null,string|null]|null, agent:[string|null,string|null]|null},
|
|
322
|
+
* fresh: boolean,
|
|
323
|
+
* }>}
|
|
324
|
+
*/
|
|
325
|
+
export async function readBannerInputs({ repoRoot, memoryDir, learningsPath, now, evolveDecay } = {}) {
|
|
326
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
327
|
+
throw new TypeError('readBannerInputs: repoRoot is required (absolute path)');
|
|
328
|
+
}
|
|
329
|
+
const memDir = typeof memoryDir === 'string' && memoryDir.length > 0
|
|
330
|
+
? memoryDir
|
|
331
|
+
: resolveMemoryDir();
|
|
332
|
+
const learningsFile = typeof learningsPath === 'string' && learningsPath.length > 0
|
|
333
|
+
? learningsPath
|
|
334
|
+
: path.join(repoRoot, '.orchestrator', 'metrics', 'learnings.jsonl');
|
|
335
|
+
const clock = now instanceof Date ? now : new Date();
|
|
336
|
+
|
|
337
|
+
// --- Top learnings ---------------------------------------------------------
|
|
338
|
+
let topLearnings = [];
|
|
339
|
+
try {
|
|
340
|
+
const surfaced = await surfaceTopN(learningsFile, 5, {
|
|
341
|
+
now: clock,
|
|
342
|
+
// Thread the documented decay knobs (#670) — without this, decay-enabled /
|
|
343
|
+
// decay-half-life-days are parsed but never applied to the banner ranking.
|
|
344
|
+
decay: decayOptsFromConfig(evolveDecay),
|
|
345
|
+
});
|
|
346
|
+
if (Array.isArray(surfaced)) {
|
|
347
|
+
topLearnings = surfaced
|
|
348
|
+
.filter((e) => e && typeof e === 'object')
|
|
349
|
+
.map((e) => ({
|
|
350
|
+
// Fallback to `id` (kebab-case slug, always present) when subject is missing
|
|
351
|
+
// or empty — prevents empty `• (0.9, type)` lines in the banner.
|
|
352
|
+
// Source: deep-2026-05-23-deep-3 Q4 session-reviewer LOW finding (15 historical
|
|
353
|
+
// learnings entries have subject:null and would have rendered blank).
|
|
354
|
+
subject:
|
|
355
|
+
typeof e.subject === 'string' && e.subject.length > 0
|
|
356
|
+
? e.subject
|
|
357
|
+
: typeof e.id === 'string'
|
|
358
|
+
? e.id
|
|
359
|
+
: '',
|
|
360
|
+
confidence: typeof e.confidence === 'number' ? e.confidence : 0,
|
|
361
|
+
type: typeof e.type === 'string' ? e.type : 'unknown',
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
topLearnings = [];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --- Stats -----------------------------------------------------------------
|
|
369
|
+
let stats;
|
|
370
|
+
try {
|
|
371
|
+
const signals = await readDreamSignals({ repoRoot, memoryDir: memDir });
|
|
372
|
+
const memoryFiles = await countMemoryFiles(memDir);
|
|
373
|
+
// We deliberately use the actual sessions.jsonl path under repoRoot for
|
|
374
|
+
// the count of "sessions ever" — readDreamSignals exposes sessionsFilePath
|
|
375
|
+
// computed the same way, so we re-derive locally to keep this dependency
|
|
376
|
+
// surface lean and explicit.
|
|
377
|
+
const sessionsPath = path.join(repoRoot, '.orchestrator', 'metrics', 'sessions.jsonl');
|
|
378
|
+
const sessionsEver = await countJsonlLines(sessionsPath);
|
|
379
|
+
|
|
380
|
+
let daysSinceCleanup = null;
|
|
381
|
+
if (typeof signals?.lastCleanupAt === 'string' && signals.lastCleanupAt.length > 0) {
|
|
382
|
+
const cleanupMs = Date.parse(signals.lastCleanupAt);
|
|
383
|
+
if (Number.isFinite(cleanupMs)) {
|
|
384
|
+
const deltaMs = clock.getTime() - cleanupMs;
|
|
385
|
+
daysSinceCleanup = Math.max(0, Math.floor(deltaMs / 86_400_000));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
stats = { memoryFiles, sessionsEver, daysSinceCleanup };
|
|
390
|
+
} catch {
|
|
391
|
+
// Fall back to zero-state stats so the banner still renders sensibly
|
|
392
|
+
// when the .orchestrator dir is missing entirely.
|
|
393
|
+
stats = { memoryFiles: 0, sessionsEver: 0, daysSinceCleanup: null };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// --- Peer-card excerpts ----------------------------------------------------
|
|
397
|
+
let peerExcerpts;
|
|
398
|
+
try {
|
|
399
|
+
const cards = await readPeerCards(repoRoot, { now: clock });
|
|
400
|
+
const userExcerpt =
|
|
401
|
+
cards?.user && typeof cards.user.body === 'string'
|
|
402
|
+
? _extractCardExcerpt(cards.user.body)
|
|
403
|
+
: null;
|
|
404
|
+
const agentExcerpt =
|
|
405
|
+
cards?.agent && typeof cards.agent.body === 'string'
|
|
406
|
+
? _extractCardExcerpt(cards.agent.body)
|
|
407
|
+
: null;
|
|
408
|
+
|
|
409
|
+
// Normalize [null, null] tuples to null so the formatter can simply
|
|
410
|
+
// check truthiness — both representations were spec-allowed but the
|
|
411
|
+
// formatter is cleaner with one canonical "absent" sentinel.
|
|
412
|
+
const normalize = (t) => {
|
|
413
|
+
if (!Array.isArray(t)) return null;
|
|
414
|
+
if (t[0] === null && t[1] === null) return null;
|
|
415
|
+
return t;
|
|
416
|
+
};
|
|
417
|
+
peerExcerpts = {
|
|
418
|
+
user: normalize(userExcerpt),
|
|
419
|
+
agent: normalize(agentExcerpt),
|
|
420
|
+
};
|
|
421
|
+
} catch {
|
|
422
|
+
peerExcerpts = { user: null, agent: null };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const fresh = topLearnings.length === 0 && (stats?.sessionsEver ?? 0) === 0;
|
|
426
|
+
|
|
427
|
+
return { topLearnings, stats, peerExcerpts, fresh };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Top-level entry point
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Render the Phase 6.7 banner string. Returns '' when the banner is disabled
|
|
436
|
+
* via Session Config (`config.persistence === false`, or
|
|
437
|
+
* `config.memory.banner.enabled === false`). The caller — session-start
|
|
438
|
+
* Phase 6.7 — is responsible for printing.
|
|
439
|
+
*
|
|
440
|
+
* Defensive against partial / malformed config: a missing `memory.banner`
|
|
441
|
+
* subtree is treated as enabled (default-on), matching the AC.
|
|
442
|
+
*
|
|
443
|
+
* @param {object} opts
|
|
444
|
+
* @param {string} opts.repoRoot
|
|
445
|
+
* @param {string} [opts.memoryDir]
|
|
446
|
+
* @param {object} opts.config — parseSessionConfig result
|
|
447
|
+
* @param {Date} [opts.now]
|
|
448
|
+
* @returns {Promise<string>}
|
|
449
|
+
*/
|
|
450
|
+
export async function renderMemoryBanner(opts = {}) {
|
|
451
|
+
const { repoRoot, memoryDir, config, now } = opts;
|
|
452
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
453
|
+
throw new TypeError('renderMemoryBanner: repoRoot is required (absolute path)');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Silent no-op when persistence is off or banner is explicitly disabled.
|
|
457
|
+
if (config && config.persistence === false) return '';
|
|
458
|
+
if (
|
|
459
|
+
config
|
|
460
|
+
&& typeof config.memory === 'object'
|
|
461
|
+
&& config.memory !== null
|
|
462
|
+
&& typeof config.memory.banner === 'object'
|
|
463
|
+
&& config.memory.banner !== null
|
|
464
|
+
&& config.memory.banner.enabled === false
|
|
465
|
+
) {
|
|
466
|
+
return '';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const inputs = await readBannerInputs({
|
|
470
|
+
repoRoot,
|
|
471
|
+
memoryDir,
|
|
472
|
+
now,
|
|
473
|
+
// `config['evolve.decay']` is the kebab-case block from _parseEvolveDecay;
|
|
474
|
+
// readBannerInputs bridges it to surfaceTopN's camelCase opts.decay (#670).
|
|
475
|
+
evolveDecay: config && config['evolve.decay'],
|
|
476
|
+
});
|
|
477
|
+
return _formatBanner(inputs);
|
|
478
|
+
}
|