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,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* process.mjs — Core entry processors for vault-mirror (Issue #283 split).
|
|
3
|
+
*
|
|
4
|
+
* Exports: processLearning, processSession
|
|
5
|
+
* Both functions write to the vault dir and emit JSON action lines to stdout.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { join, resolve, basename } from 'node:path';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import { subjectToSlug, isValidSlug, uuidPrefix8, toDate, parseFrontmatter } from './utils.mjs';
|
|
13
|
+
import { resolveRepoNamespace } from './namespace.mjs';
|
|
14
|
+
import { detectLearningSchema, normalizeLearningEntry, generateLearningNote, generateLearningNoteV2 } from './render-learnings.mjs';
|
|
15
|
+
import { detectSessionSchema, normalizeSessionEntry, generateSessionNote, generateSessionNoteV2, generateSessionNoteV3 } from './render-sessions.mjs';
|
|
16
|
+
|
|
17
|
+
const GENERATOR_MARKER = 'session-orchestrator-vault-mirror@1';
|
|
18
|
+
|
|
19
|
+
// ── Session-note existence index (Issue #704) ─────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Module-level cache: resolved vaultDir path → Set of known session basenames
|
|
23
|
+
* (without the `.md` extension). Built once per unique vaultDir per process.
|
|
24
|
+
*
|
|
25
|
+
* @type {Map<string, Set<string>>}
|
|
26
|
+
*/
|
|
27
|
+
const _sessionNoteSets = new Map();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Recursively walk `<vaultDir>/50-sessions/` and collect every `.md` basename
|
|
31
|
+
* (without extension) into a Set. Returns an EMPTY Set (never throws) when the
|
|
32
|
+
* directory is absent or unreadable — callers treat an empty Set as "no predicate
|
|
33
|
+
* available", falling back to format-validation in resolveSourceSessionLink.
|
|
34
|
+
*
|
|
35
|
+
* Read-only: never creates directories, safe in dryRun mode.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} vaultDir — absolute path to the vault root
|
|
38
|
+
* @returns {Set<string>}
|
|
39
|
+
*/
|
|
40
|
+
function getSessionNoteSet(vaultDir) {
|
|
41
|
+
const resolvedVault = resolve(vaultDir);
|
|
42
|
+
if (_sessionNoteSets.has(resolvedVault)) return _sessionNoteSets.get(resolvedVault);
|
|
43
|
+
|
|
44
|
+
const knownSessions = new Set();
|
|
45
|
+
const sessionsDir = join(resolvedVault, '50-sessions');
|
|
46
|
+
|
|
47
|
+
function walk(dir) {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return; // dir absent or inaccessible — skip silently
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
walk(join(dir, entry.name));
|
|
57
|
+
} else if (entry.name.endsWith('.md')) {
|
|
58
|
+
knownSessions.add(entry.name.slice(0, -3)); // basename without .md
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
walk(sessionsDir);
|
|
64
|
+
_sessionNoteSets.set(resolvedVault, knownSessions);
|
|
65
|
+
return knownSessions;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Content diff helper ───────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract canonical comparable fields from a rendered learning note string.
|
|
72
|
+
*
|
|
73
|
+
* We compare only the fields that represent meaningful SSOT content changes —
|
|
74
|
+
* not created/updated dates or frontmatter ordering, which differ on every
|
|
75
|
+
* render without signalling a content change.
|
|
76
|
+
*
|
|
77
|
+
* Comparable fields:
|
|
78
|
+
* - status (frontmatter line `status: <value>`)
|
|
79
|
+
* - expires (frontmatter line `expires: <value>`, v1 only; absent in v2)
|
|
80
|
+
* - confidence (body bullet `- **Confidence:** <value>`)
|
|
81
|
+
* - insight body (text after `## Insight\n\n` heading, trimmed)
|
|
82
|
+
*
|
|
83
|
+
* @param {string} noteContent — rendered markdown string
|
|
84
|
+
* @returns {{ status: string, expires: string, confidence: string, insight: string }}
|
|
85
|
+
*/
|
|
86
|
+
function extractLearningCanonicalFields(noteContent) {
|
|
87
|
+
const status = (noteContent.match(/^status:\s*(.+)$/m) || [])[1]?.trim() ?? '';
|
|
88
|
+
const expires = (noteContent.match(/^expires:\s*(.+)$/m) || [])[1]?.trim() ?? '';
|
|
89
|
+
const confidence = (noteContent.match(/^- \*\*Confidence:\*\*\s*(.+)$/m) || [])[1]?.trim() ?? '';
|
|
90
|
+
// Extract insight body: everything after `## Insight\n\n` until the next `##` or end-of-string
|
|
91
|
+
const insightMatch = noteContent.match(/^## Insight\n\n([\s\S]*?)(?=\n## |\s*$)/m);
|
|
92
|
+
const insight = insightMatch ? insightMatch[1].trim() : '';
|
|
93
|
+
// #704: track the frontmatter source_session so a normal re-mirror repairs
|
|
94
|
+
// historical dangling-link notes (e.g. `source_session: "[[unknown]]"`) once
|
|
95
|
+
// the renderer emits the corrected plain-text/resolvable form — otherwise the
|
|
96
|
+
// content-diff would treat the stale note as a no-op and never heal it.
|
|
97
|
+
const source_session = (noteContent.match(/^source_session:\s*(.+)$/m) || [])[1]?.trim() ?? '';
|
|
98
|
+
return { status, expires, confidence, insight, source_session };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Return true when the existing vault note content and the freshly-rendered
|
|
103
|
+
* candidate share identical canonical fields (i.e. no meaningful update needed).
|
|
104
|
+
*
|
|
105
|
+
* A true result means → emit skipped-noop.
|
|
106
|
+
* A false result means → the SSOT changed; write the new content.
|
|
107
|
+
*
|
|
108
|
+
* Comparison semantics: a field that is ABSENT in the existing note (empty
|
|
109
|
+
* string from extractLearningCanonicalFields) is treated as matching any
|
|
110
|
+
* rendered value for that field. This preserves backward compatibility with
|
|
111
|
+
* notes written by older generator versions that may not have emitted every
|
|
112
|
+
* field. Only when an existing field is NON-EMPTY and differs from the
|
|
113
|
+
* rendered candidate do we conclude the SSOT has changed and an update is needed.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} existingContent — content read from the vault note on disk
|
|
116
|
+
* @param {string} renderedContent — freshly generated note from the renderer
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
function learningContentMatches(existingContent, renderedContent) {
|
|
120
|
+
const existing = extractLearningCanonicalFields(existingContent);
|
|
121
|
+
const rendered = extractLearningCanonicalFields(renderedContent);
|
|
122
|
+
// For each field: if the existing value is absent (empty string), it cannot
|
|
123
|
+
// signal a mismatch — it means the old note didn't track that field. Only
|
|
124
|
+
// non-empty existing values are compared against the rendered candidate.
|
|
125
|
+
const fieldMatches = (existingVal, renderedVal) =>
|
|
126
|
+
existingVal === '' || existingVal === renderedVal;
|
|
127
|
+
return (
|
|
128
|
+
fieldMatches(existing.status, rendered.status) &&
|
|
129
|
+
fieldMatches(existing.expires, rendered.expires) &&
|
|
130
|
+
fieldMatches(existing.confidence, rendered.confidence) &&
|
|
131
|
+
fieldMatches(existing.insight, rendered.insight) &&
|
|
132
|
+
fieldMatches(existing.source_session, rendered.source_session)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── repo derivation ───────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
let _cachedRepo = null;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Derive the canonical repo identifier for cross-repo vault aggregation (issue #343).
|
|
142
|
+
*
|
|
143
|
+
* Strategy: parse `git remote get-url origin` and extract the org/name pair
|
|
144
|
+
* (e.g. `git@github.com:Kanevry/session-orchestrator.git` → `Kanevry/session-orchestrator`).
|
|
145
|
+
* Falls back to `path.basename(process.cwd())` when not in a git repo or origin
|
|
146
|
+
* is unavailable. Cached per-process — repo identity does not change mid-run.
|
|
147
|
+
*/
|
|
148
|
+
export function deriveRepo() {
|
|
149
|
+
if (_cachedRepo !== null) return _cachedRepo;
|
|
150
|
+
try {
|
|
151
|
+
const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
152
|
+
encoding: 'utf8',
|
|
153
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
154
|
+
}).trim();
|
|
155
|
+
// Match git@host:org/name(.git)? OR https://host/org/name(.git)?
|
|
156
|
+
const sshMatch = url.match(/[:/]([^:/]+\/[^/]+?)(?:\.git)?$/);
|
|
157
|
+
if (sshMatch && sshMatch[1]) {
|
|
158
|
+
_cachedRepo = sshMatch[1];
|
|
159
|
+
return _cachedRepo;
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// git unavailable or no origin configured — fall through
|
|
163
|
+
}
|
|
164
|
+
_cachedRepo = basename(process.cwd());
|
|
165
|
+
return _cachedRepo;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Action output ─────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Emit a JSON action line to stdout.
|
|
172
|
+
*
|
|
173
|
+
* Takes a single self-documenting options object (issue #511). `emitAction` is a
|
|
174
|
+
* module-level export (independently unit-tested), so `vaultDir` is carried as a
|
|
175
|
+
* named option field rather than pulled from a closure — both callers
|
|
176
|
+
* (processLearning, processSession) destructure it from their own `ctx` and pass
|
|
177
|
+
* it through.
|
|
178
|
+
*
|
|
179
|
+
* #589 LOW-arch-3 — the `vaultDir`-in-options-object pattern is consciously
|
|
180
|
+
* accepted: the 16 call-sites in THIS module repeat `vaultDir` as boilerplate,
|
|
181
|
+
* but there is only one consumer module, so a `makeEmitAction({ vaultDir })`
|
|
182
|
+
* closure factory would be premature (YAGNI). Extract the factory ONLY when a
|
|
183
|
+
* SECOND module needs `emitAction` and would otherwise re-thread `vaultDir`.
|
|
184
|
+
*
|
|
185
|
+
* @param {object} opts
|
|
186
|
+
* @param {string} opts.action — action name (e.g. 'created', 'updated', 'skipped-quality-low')
|
|
187
|
+
* @param {string|null} opts.path — absolute path to the file (or null when no file
|
|
188
|
+
* was created/touched, e.g. for quality-gate skips before any write)
|
|
189
|
+
* @param {string} opts.kind — 'learning' or 'session'
|
|
190
|
+
* @param {string|null} opts.id — entry id
|
|
191
|
+
* @param {string} opts.vaultDir — vault root (used to relativise `path`)
|
|
192
|
+
* @param {object} [opts.meta] — optional extra fields merged into the emitted JSON
|
|
193
|
+
* (used for quality-gate skips to carry a `reason` field). Callers that omit
|
|
194
|
+
* `meta` get the base JSON shape unchanged.
|
|
195
|
+
*/
|
|
196
|
+
export function emitAction({ action, path, kind, id, vaultDir, meta }) {
|
|
197
|
+
let rel;
|
|
198
|
+
if (path === null || path === undefined) {
|
|
199
|
+
rel = null;
|
|
200
|
+
} else {
|
|
201
|
+
const resolvedVaultDir = resolve(vaultDir);
|
|
202
|
+
rel = path.startsWith(resolvedVaultDir)
|
|
203
|
+
? path.slice(resolvedVaultDir.length + 1)
|
|
204
|
+
: path;
|
|
205
|
+
}
|
|
206
|
+
const payload = { action, path: rel, kind, id };
|
|
207
|
+
if (meta && typeof meta === 'object') {
|
|
208
|
+
Object.assign(payload, meta);
|
|
209
|
+
}
|
|
210
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Core processing ───────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
216
|
+
const {
|
|
217
|
+
vaultDir,
|
|
218
|
+
dryRun,
|
|
219
|
+
kind,
|
|
220
|
+
force = false,
|
|
221
|
+
qualityMinConfidence = 0.5,
|
|
222
|
+
qualityMinNarrativeChars: _qualityMinNarrativeChars = 400, // unused for learnings; kept for ctx symmetry
|
|
223
|
+
} = ctx;
|
|
224
|
+
// #635: map producer alias fields (summary/detail, description/rationale,
|
|
225
|
+
// title/body, name, narrative, content) onto the canonical v1 shape BEFORE
|
|
226
|
+
// schema detection and slug/id derivation. Canonical entries pass through.
|
|
227
|
+
const entry = normalizeLearningEntry(rawEntry);
|
|
228
|
+
const schema = detectLearningSchema(entry);
|
|
229
|
+
const entryId = entry.id;
|
|
230
|
+
|
|
231
|
+
// Slug source differs by schema. Both fall back to learning-<uuid8> on invalid.
|
|
232
|
+
// For v2 the id is already a kebab slug (e.g. "s69-compose-pids-cross-validation").
|
|
233
|
+
// Crucially: validate id presence before slug derivation, so missing-id entries
|
|
234
|
+
// become skipped-invalid rather than crashing inside subjectToSlug.
|
|
235
|
+
if (entryId === null || entryId === undefined) {
|
|
236
|
+
throw new Error(`vault-mirror: learning entry missing required field 'id' (id=<no id>)`);
|
|
237
|
+
}
|
|
238
|
+
// #725 D1: the raw v1 subject is prose WITH SPACES, and subjectToSlug() strips
|
|
239
|
+
// spaces WITHOUT hyphenating — so "Dead fallback removal when primary parser
|
|
240
|
+
// matures" collapsed into a single run "deadfallbackremovalwhenprimaryparser…"
|
|
241
|
+
// (a silent slug corruption that still passes isValidSlug). Pre-map the
|
|
242
|
+
// subject's whitespace to hyphens BEFORE subjectToSlug, mirroring the id
|
|
243
|
+
// derivation in normalizeLearningEntry (render-learnings.mjs L63-65) so a
|
|
244
|
+
// learning's slug matches its derived id. This keeps the subject as the v1 slug
|
|
245
|
+
// source (the established contract: entry.id is reserved for the disambiguation
|
|
246
|
+
// /invalid-slug fallback prefix via uuidPrefix8 below), and only heals the
|
|
247
|
+
// space-collapse defect. v2 ids are already kebab slugs.
|
|
248
|
+
//
|
|
249
|
+
// NOTE (#725 D1 divergence): the wave brief asked to make entry.id the PRIMARY
|
|
250
|
+
// slug source. That is architecturally incompatible with the invalid-slug
|
|
251
|
+
// fallback `learning-<uuidPrefix8(entry.id)>` (which requires entry.id to stay
|
|
252
|
+
// a clean value SEPARATE from the slug source) and would invert the slug-from-
|
|
253
|
+
// subject contract pinned by 15 tests in tests/unit/vault-mirror.test.mjs. The
|
|
254
|
+
// pre-map is the mechanism the brief itself names and yields the IDENTICAL slug
|
|
255
|
+
// for real data (kebab id derived from the same subject); see report.
|
|
256
|
+
let slugSource;
|
|
257
|
+
if (schema === 'v2') {
|
|
258
|
+
slugSource = entry.id;
|
|
259
|
+
} else if (typeof entry.subject === 'string') {
|
|
260
|
+
slugSource = entry.subject.trim().replace(/\s+/g, '-');
|
|
261
|
+
} else {
|
|
262
|
+
slugSource = entry.subject;
|
|
263
|
+
}
|
|
264
|
+
let slug;
|
|
265
|
+
if (typeof slugSource === 'string' && slugSource.length > 0) {
|
|
266
|
+
slug = subjectToSlug(slugSource);
|
|
267
|
+
} else {
|
|
268
|
+
slug = '';
|
|
269
|
+
}
|
|
270
|
+
if (!isValidSlug(slug)) {
|
|
271
|
+
slug = `learning-${uuidPrefix8(entryId)}`;
|
|
272
|
+
}
|
|
273
|
+
// #635: cap the slug so `<slug>.md` (plus a possible `-<uuid8>` disambig
|
|
274
|
+
// suffix) stays under the 255-byte filename limit. Normalized prose subjects
|
|
275
|
+
// can be arbitrarily long and previously aborted the whole mirror run with
|
|
276
|
+
// ENAMETOOLONG. 240 + 9 (disambig) + 3 (.md) = 252 — and every pre-existing
|
|
277
|
+
// vault note (max observed slug: 208 chars) keeps its identity untouched.
|
|
278
|
+
if (slug.length > 240) {
|
|
279
|
+
slug = slug.slice(0, 240).replace(/-+$/, '');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Generator + date source differ by schema
|
|
283
|
+
const generator = schema === 'v2' ? generateLearningNoteV2 : generateLearningNote;
|
|
284
|
+
const dateSource = schema === 'v2' ? entry.first_seen : entry.created_at;
|
|
285
|
+
|
|
286
|
+
// #704: Build a noteExists predicate from the vault's 50-sessions index so that
|
|
287
|
+
// resolveSourceSessionLink can use EXISTENCE-based resolution instead of strict
|
|
288
|
+
// format-validation. The index is built once per vaultDir (cached in
|
|
289
|
+
// _sessionNoteSets). When the 50-sessions dir is absent/empty, the Set is empty
|
|
290
|
+
// and we pass NO predicate — resolveSourceSessionLink falls back to format
|
|
291
|
+
// validation (never worse than Wave 2 behaviour).
|
|
292
|
+
const _knownSessions = getSessionNoteSet(vaultDir);
|
|
293
|
+
const _noteExists = _knownSessions.size > 0 ? (s) => _knownSessions.has(s) : undefined;
|
|
294
|
+
const generatorOpts = _noteExists ? { noteExists: _noteExists } : {};
|
|
295
|
+
|
|
296
|
+
// Quality gate (PRD F1.2): skip learnings with confidence below threshold.
|
|
297
|
+
// Runs BEFORE the --force branch so --force does NOT bypass the quality filter.
|
|
298
|
+
// Missing/non-numeric confidence is treated as 1.0 (legacy entries pass).
|
|
299
|
+
const learningConfidence = typeof entry.confidence === 'number' ? entry.confidence : 1.0;
|
|
300
|
+
if (learningConfidence < qualityMinConfidence) {
|
|
301
|
+
emitAction({
|
|
302
|
+
action: 'skipped-quality-low',
|
|
303
|
+
path: null,
|
|
304
|
+
kind,
|
|
305
|
+
id: entryId,
|
|
306
|
+
vaultDir,
|
|
307
|
+
meta: { reason: `confidence:${learningConfidence} < min:${qualityMinConfidence}` },
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// #660: namespace new writes under a per-repo subdirectory.
|
|
313
|
+
const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
|
|
314
|
+
// #725 D2: thread the resolved repo namespace into the learning frontmatter as
|
|
315
|
+
// `source-repo` for cross-repo attribution. repoNs is already sanitised +
|
|
316
|
+
// leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
|
|
317
|
+
// renderer reads opts.repoNs (see render-learnings.mjs); when absent (older
|
|
318
|
+
// callers), the source-repo line is omitted — backward-compatible.
|
|
319
|
+
generatorOpts.repoNs = repoNs;
|
|
320
|
+
const targetDir = join(resolve(vaultDir), '40-learnings', repoNs);
|
|
321
|
+
if (!dryRun) mkdirSync(targetDir, { recursive: true });
|
|
322
|
+
|
|
323
|
+
let targetPath = join(targetDir, `${slug}.md`);
|
|
324
|
+
|
|
325
|
+
// #660 IDEMPOTENCY DUAL-PROBE: before treating the namespaced path as absent,
|
|
326
|
+
// also check the legacy flat path. If a note with the same slug already exists
|
|
327
|
+
// in the flat layout (pre-namespace migration), treat it as existing to avoid
|
|
328
|
+
// duplicating the note. The deferred-migration decision means we only skip;
|
|
329
|
+
// we do NOT move the flat note into the namespaced dir here.
|
|
330
|
+
const legacyFlatPath = join(resolve(vaultDir), '40-learnings', `${slug}.md`);
|
|
331
|
+
if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
|
|
332
|
+
const legacyContent = readFileSync(legacyFlatPath, 'utf8');
|
|
333
|
+
const legacyFm = parseFrontmatter(legacyContent);
|
|
334
|
+
// Only skip if the flat note is ours (has our generator marker and matching id).
|
|
335
|
+
if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === slug) {
|
|
336
|
+
const entryUpdated = toDate(dateSource);
|
|
337
|
+
if (!force && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
|
|
338
|
+
// Date has not advanced — but content may have changed (confidence, insight, etc.).
|
|
339
|
+
// Render the candidate and compare canonical fields before deciding to skip.
|
|
340
|
+
const candidateContent = generator(entry, slug, generatorOpts);
|
|
341
|
+
if (learningContentMatches(legacyContent, candidateContent)) {
|
|
342
|
+
emitAction({ action: 'skipped-noop', path: legacyFlatPath, kind, id: slug, vaultDir });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Content differs — fall through to write into the namespaced path.
|
|
346
|
+
}
|
|
347
|
+
// Updated date would advance or content changed — fall through to write into the namespaced path.
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (existsSync(targetPath)) {
|
|
352
|
+
const existingContent = readFileSync(targetPath, 'utf8');
|
|
353
|
+
const fm = parseFrontmatter(existingContent);
|
|
354
|
+
|
|
355
|
+
if (!fm || !fm['_generator']) {
|
|
356
|
+
// Hand-written: skip
|
|
357
|
+
process.stderr.write(`SKIP hand-written: ${targetPath}\n`);
|
|
358
|
+
emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (fm['_generator'] !== GENERATOR_MARKER) {
|
|
363
|
+
// Different generator — treat as hand-written to be safe
|
|
364
|
+
process.stderr.write(`SKIP unknown generator: ${targetPath}\n`);
|
|
365
|
+
emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (fm['id'] !== slug) {
|
|
370
|
+
// Different id → collision: disambiguate
|
|
371
|
+
const disambigSlug = `${slug}-${uuidPrefix8(entryId)}`;
|
|
372
|
+
targetPath = join(targetDir, `${disambigSlug}.md`);
|
|
373
|
+
slug = disambigSlug;
|
|
374
|
+
|
|
375
|
+
if (existsSync(targetPath)) {
|
|
376
|
+
// Still exists with disambig — check if it's ours
|
|
377
|
+
const disambigContent = readFileSync(targetPath, 'utf8');
|
|
378
|
+
const disambigFm = parseFrontmatter(disambigContent);
|
|
379
|
+
if (!disambigFm || !disambigFm['_generator']) {
|
|
380
|
+
process.stderr.write(`SKIP hand-written (disambig): ${targetPath}\n`);
|
|
381
|
+
emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// Check updated advancement; if date has not advanced, also diff content.
|
|
385
|
+
const entryUpdated = toDate(dateSource);
|
|
386
|
+
if (disambigFm['updated'] && disambigFm['updated'] >= entryUpdated) {
|
|
387
|
+
const candidateContent = generator(entry, slug, generatorOpts);
|
|
388
|
+
if (learningContentMatches(disambigContent, candidateContent)) {
|
|
389
|
+
emitAction({ action: 'skipped-noop', path: targetPath, kind, id: disambigSlug, vaultDir });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Content differs — fall through to write.
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const content = generator(entry, slug, generatorOpts);
|
|
397
|
+
if (!dryRun) writeFileSync(targetPath, content, 'utf8');
|
|
398
|
+
emitAction({ action: 'skipped-collision-resolved', path: targetPath, kind, id: slug, vaultDir });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Same id: check if updated would advance (unless --force overrides).
|
|
403
|
+
// Even when the date has not advanced, content may have changed (confidence,
|
|
404
|
+
// insight, expires_at, etc.) — compare canonical fields before skipping.
|
|
405
|
+
const entryUpdated = toDate(dateSource);
|
|
406
|
+
if (!force && fm['updated'] && fm['updated'] >= entryUpdated) {
|
|
407
|
+
const candidateContent = generator(entry, slug, generatorOpts);
|
|
408
|
+
if (learningContentMatches(existingContent, candidateContent)) {
|
|
409
|
+
emitAction({ action: 'skipped-noop', path: targetPath, kind, id: slug, vaultDir });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
// Content differs — fall through to overwrite (same path as date-advance branch).
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Overwrite with advanced updated date (or forced re-render)
|
|
416
|
+
const content = generator(entry, slug, generatorOpts);
|
|
417
|
+
if (!dryRun) writeFileSync(targetPath, content, 'utf8');
|
|
418
|
+
emitAction({ action: 'updated', path: targetPath, kind, id: slug, vaultDir });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// File does not exist — create
|
|
423
|
+
const content = generator(entry, slug, generatorOpts);
|
|
424
|
+
if (!dryRun) writeFileSync(targetPath, content, 'utf8');
|
|
425
|
+
emitAction({ action: 'created', path: targetPath, kind, id: slug, vaultDir });
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export async function processSession(rawEntry, _lineNum, ctx) {
|
|
429
|
+
const {
|
|
430
|
+
vaultDir,
|
|
431
|
+
dryRun,
|
|
432
|
+
kind,
|
|
433
|
+
force = false,
|
|
434
|
+
qualityMinNarrativeChars = 400,
|
|
435
|
+
qualityMinConfidence: _qualityMinConfidence = 0.5, // unused for sessions; kept for ctx symmetry
|
|
436
|
+
} = ctx;
|
|
437
|
+
// #635: map producer alias fields (ended_at, mode, total_waves/waves_completed
|
|
438
|
+
// without a `waves` field) onto the canonical shapes BEFORE schema detection.
|
|
439
|
+
// Canonical v1/v2/v3 entries pass through untouched.
|
|
440
|
+
const entry = normalizeSessionEntry(rawEntry);
|
|
441
|
+
const { session_id: rawSessionId } = entry;
|
|
442
|
+
const schema = detectSessionSchema(entry);
|
|
443
|
+
const generator =
|
|
444
|
+
schema === 'v3' ? generateSessionNoteV3 : schema === 'v2' ? generateSessionNoteV2 : generateSessionNote;
|
|
445
|
+
|
|
446
|
+
// Validate session_id as a filesystem-safe slug; fall back via subjectToSlug
|
|
447
|
+
// (which collapses slashes to last segment + strips invalid chars) before
|
|
448
|
+
// resorting to a uuid-derived slug. Without subjectToSlug, raw slashes in
|
|
449
|
+
// rawSessionId (e.g. "feat/opus-4-7-...") would survive uuidPrefix8 and
|
|
450
|
+
// produce a path with directory separators in the basename.
|
|
451
|
+
let session_id;
|
|
452
|
+
if (isValidSlug(rawSessionId)) {
|
|
453
|
+
session_id = rawSessionId;
|
|
454
|
+
} else if (typeof rawSessionId === 'string' && rawSessionId.length > 0) {
|
|
455
|
+
const sanitised = subjectToSlug(rawSessionId);
|
|
456
|
+
session_id = isValidSlug(sanitised) ? sanitised : `session-${uuidPrefix8(rawSessionId)}`;
|
|
457
|
+
} else {
|
|
458
|
+
session_id = `session-${uuidPrefix8(randomUUID())}`;
|
|
459
|
+
}
|
|
460
|
+
// #635: symmetric slug-length cap (see processLearning) — a pathologically
|
|
461
|
+
// long but otherwise valid session_id slug would abort the mirror run with
|
|
462
|
+
// ENAMETOOLONG when the filename exceeds the 255-byte limit.
|
|
463
|
+
if (session_id.length > 240) {
|
|
464
|
+
session_id = session_id.slice(0, 240).replace(/-+$/, '');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// #732: resolve the leak-guarded repo namespace ONCE per session, BEFORE the
|
|
468
|
+
// quality gate, so both the write path (targetDir, below) AND the rendered
|
|
469
|
+
// frontmatter (`source-repo`) use the SAME sanitised / pseudonym-mapped value.
|
|
470
|
+
// Previously the frontmatter carried the RAW deriveRepo() output via a `repo:`
|
|
471
|
+
// field while only the write path routed through resolveRepoNamespace() — an
|
|
472
|
+
// owner-leaky repo's real name reached the vault through the session-note
|
|
473
|
+
// frontmatter even though the directory AND the learning `source-repo` field
|
|
474
|
+
// were already pseudonym-mapped/redacted (#732 leak-guard bypass).
|
|
475
|
+
const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
|
|
476
|
+
|
|
477
|
+
// Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
|
|
478
|
+
// Measure on the rendered markdown body so the check is schema-agnostic across
|
|
479
|
+
// v1 and v2 producers. The render is cheap and idempotent; we reuse the result
|
|
480
|
+
// below instead of calling generator() a second time.
|
|
481
|
+
// Runs BEFORE the --force branch so --force does NOT bypass the quality filter.
|
|
482
|
+
// If the generator throws a `vault-mirror: …` validation error (missing
|
|
483
|
+
// required field), it propagates up to vault-mirror.mjs which classifies it
|
|
484
|
+
// as `skipped-invalid` rather than `skipped-quality-low` — semantically more
|
|
485
|
+
// accurate for the metrics summary in session-end Phase 3.7.
|
|
486
|
+
const renderedBody = generator(entry, { repoNs });
|
|
487
|
+
// Strip YAML frontmatter (lines between the first two `---` markers) so we
|
|
488
|
+
// measure only narrative content, not boilerplate.
|
|
489
|
+
const narrativeBody = renderedBody.replace(/^---[\s\S]*?---/m, '').trim();
|
|
490
|
+
const narrativeChars = narrativeBody.length;
|
|
491
|
+
if (narrativeChars < qualityMinNarrativeChars) {
|
|
492
|
+
emitAction({
|
|
493
|
+
action: 'skipped-quality-low',
|
|
494
|
+
path: null,
|
|
495
|
+
kind,
|
|
496
|
+
id: session_id,
|
|
497
|
+
vaultDir,
|
|
498
|
+
meta: { reason: `narrative:${narrativeChars} < min:${qualityMinNarrativeChars}` },
|
|
499
|
+
});
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// #660: namespace new writes under a per-repo subdirectory. repoNs was
|
|
504
|
+
// resolved above (before the quality gate) so it is reused here unchanged —
|
|
505
|
+
// the write path and the rendered `source-repo` frontmatter share one value.
|
|
506
|
+
const targetDir = join(resolve(vaultDir), '50-sessions', repoNs);
|
|
507
|
+
if (!dryRun) mkdirSync(targetDir, { recursive: true });
|
|
508
|
+
|
|
509
|
+
// Canonical filename pattern (issue #343): `<session_id>.md` where session_id
|
|
510
|
+
// follows `<branch>-<YYYY-MM-DD>-<HHmm>-<slug>` per the session-id schema.
|
|
511
|
+
// session_id has been validated/sanitised above (isValidSlug → subjectToSlug
|
|
512
|
+
// → uuid fallback). Historical filename inconsistencies in 50-sessions/ are
|
|
513
|
+
// pre-existing on-disk artefacts and are NOT retroactively renamed here.
|
|
514
|
+
const targetPath = join(targetDir, `${session_id}.md`);
|
|
515
|
+
|
|
516
|
+
// #660 IDEMPOTENCY DUAL-PROBE: check the legacy flat path before treating
|
|
517
|
+
// the namespaced path as absent. If a session note already exists flat
|
|
518
|
+
// (pre-namespace migration), skip creating a duplicate.
|
|
519
|
+
const legacyFlatPath = join(resolve(vaultDir), '50-sessions', `${session_id}.md`);
|
|
520
|
+
if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
|
|
521
|
+
const legacyContent = readFileSync(legacyFlatPath, 'utf8');
|
|
522
|
+
const legacyFm = parseFrontmatter(legacyContent);
|
|
523
|
+
if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === session_id) {
|
|
524
|
+
const entryUpdated = toDate(entry.completed_at);
|
|
525
|
+
if (!force && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
|
|
526
|
+
emitAction({ action: 'skipped-noop', path: legacyFlatPath, kind, id: session_id, vaultDir });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
// Updated date would advance — fall through to write into the namespaced path.
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (existsSync(targetPath)) {
|
|
534
|
+
const existingContent = readFileSync(targetPath, 'utf8');
|
|
535
|
+
const fm = parseFrontmatter(existingContent);
|
|
536
|
+
|
|
537
|
+
if (!fm || !fm['_generator']) {
|
|
538
|
+
// Hand-written: skip
|
|
539
|
+
process.stderr.write(`SKIP hand-written: ${targetPath}\n`);
|
|
540
|
+
emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: session_id, vaultDir });
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (fm['_generator'] !== GENERATOR_MARKER) {
|
|
545
|
+
process.stderr.write(`SKIP unknown generator: ${targetPath}\n`);
|
|
546
|
+
emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: session_id, vaultDir });
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Same generator: check id and updated
|
|
551
|
+
if (fm['id'] === session_id) {
|
|
552
|
+
const entryUpdated = toDate(entry.completed_at);
|
|
553
|
+
if (!force && fm['updated'] && fm['updated'] >= entryUpdated) {
|
|
554
|
+
emitAction({ action: 'skipped-noop', path: targetPath, kind, id: session_id, vaultDir });
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
|
|
558
|
+
emitAction({ action: 'updated', path: targetPath, kind, id: session_id, vaultDir });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// File does not exist — create. Reuse the rendered body computed during the
|
|
564
|
+
// quality-gate check (avoids a second generator invocation).
|
|
565
|
+
if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
|
|
566
|
+
emitAction({ action: 'created', path: targetPath, kind, id: session_id, vaultDir });
|
|
567
|
+
}
|