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,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markdown-writer.mjs — Render and write the GitLab Portfolio dashboard file.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* GENERATOR_MARKER — frontmatter sentinel
|
|
6
|
+
* renderPortfolio — pure render: Map<repo, summary> → markdown string
|
|
7
|
+
* writePortfolio — idempotent write with skip-handwritten / skip-noop / dry-run
|
|
8
|
+
*
|
|
9
|
+
* Skip-on-manual-edit: if the existing file's _generator is absent or differs,
|
|
10
|
+
* return { action: 'skipped-handwritten' } without writing.
|
|
11
|
+
*
|
|
12
|
+
* Skip-noop: if the rendered content (modulo the `updated:` timestamp) matches
|
|
13
|
+
* the existing content byte-for-byte, return { action: 'skipped-noop' }.
|
|
14
|
+
*
|
|
15
|
+
* Dry-run: when dryRun: true, never write; return { action: 'dry-run' }.
|
|
16
|
+
*
|
|
17
|
+
* Created-preservation: if the existing file has a `created:` frontmatter field,
|
|
18
|
+
* propagate it into the new render.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
22
|
+
import { dirname } from 'node:path';
|
|
23
|
+
import { parseFrontmatter } from '../vault-mirror/utils.mjs';
|
|
24
|
+
|
|
25
|
+
/** Frontmatter sentinel that identifies generator-owned files. */
|
|
26
|
+
export const GENERATOR_MARKER = 'session-orchestrator-gitlab-portfolio@1';
|
|
27
|
+
|
|
28
|
+
/** Placeholder used for noop comparison (replaces the live `updated:` value). */
|
|
29
|
+
const UPDATED_PLACEHOLDER = '__UPDATED_PLACEHOLDER__';
|
|
30
|
+
|
|
31
|
+
// ── Formatting helpers ─────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format a Date as "YYYY-MM-DD HH:MM UTC".
|
|
35
|
+
*
|
|
36
|
+
* @param {Date} date
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function formatDateUTC(date) {
|
|
40
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
41
|
+
return (
|
|
42
|
+
`${date.getUTCFullYear()}-` +
|
|
43
|
+
`${pad(date.getUTCMonth() + 1)}-` +
|
|
44
|
+
`${pad(date.getUTCDate())} ` +
|
|
45
|
+
`${pad(date.getUTCHours())}:` +
|
|
46
|
+
`${pad(date.getUTCMinutes())} UTC`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format a Date as ISO 8601 (for frontmatter fields).
|
|
52
|
+
*
|
|
53
|
+
* @param {Date} date
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function toIso(date) {
|
|
57
|
+
return date.toISOString();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format a last-activity ISO string for display. Returns '—' if absent.
|
|
62
|
+
*
|
|
63
|
+
* @param {string|null|undefined} iso
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
function fmtActivity(iso) {
|
|
67
|
+
if (!iso) return '—';
|
|
68
|
+
// Display as YYYY-MM-DD HH:MM UTC
|
|
69
|
+
try {
|
|
70
|
+
const d = new Date(iso);
|
|
71
|
+
return formatDateUTC(d);
|
|
72
|
+
} catch {
|
|
73
|
+
return iso;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format a milestone for display.
|
|
79
|
+
*
|
|
80
|
+
* @param {{ title: string, due_date: string } | null} ms
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function fmtMilestone(ms) {
|
|
84
|
+
if (!ms) return '—';
|
|
85
|
+
return ms.due_date ? `${ms.title} (${ms.due_date})` : ms.title;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Render ─────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render the portfolio markdown body from per-repo summaries.
|
|
92
|
+
*
|
|
93
|
+
* Repos are sorted alphabetically by name for stable output.
|
|
94
|
+
* topThree within each repo is already sorted by updated_at desc (from summarizeRepo).
|
|
95
|
+
*
|
|
96
|
+
* @param {Map<string, {
|
|
97
|
+
* openCount: number,
|
|
98
|
+
* criticalCount: number,
|
|
99
|
+
* staleCount: number,
|
|
100
|
+
* nextMilestone: { title: string, due_date: string } | null,
|
|
101
|
+
* lastActivity: string | null,
|
|
102
|
+
* topThree: Array<{ iid: number|string, title: string, labels: string[], url: string }>,
|
|
103
|
+
* }>} summaries
|
|
104
|
+
* @param {{
|
|
105
|
+
* now: Date,
|
|
106
|
+
* createdIso?: string,
|
|
107
|
+
* staleDays?: number,
|
|
108
|
+
* updatedPlaceholder?: string,
|
|
109
|
+
* }} opts
|
|
110
|
+
* @returns {string} full markdown (frontmatter + body)
|
|
111
|
+
*/
|
|
112
|
+
export function renderPortfolio(summaries, opts) {
|
|
113
|
+
const {
|
|
114
|
+
now,
|
|
115
|
+
createdIso,
|
|
116
|
+
staleDays = 30,
|
|
117
|
+
updatedPlaceholder,
|
|
118
|
+
} = opts;
|
|
119
|
+
|
|
120
|
+
const nowIso = toIso(now);
|
|
121
|
+
const updatedValue = updatedPlaceholder ?? nowIso;
|
|
122
|
+
const createdValue = createdIso ?? nowIso;
|
|
123
|
+
|
|
124
|
+
// Aggregate totals
|
|
125
|
+
let totalOpen = 0;
|
|
126
|
+
let totalCritical = 0;
|
|
127
|
+
let totalStale = 0;
|
|
128
|
+
const sortedRepos = [...summaries.keys()].sort((a, b) => a.localeCompare(b));
|
|
129
|
+
|
|
130
|
+
for (const repo of sortedRepos) {
|
|
131
|
+
const s = summaries.get(repo);
|
|
132
|
+
totalOpen += s.openCount;
|
|
133
|
+
totalCritical += s.criticalCount;
|
|
134
|
+
totalStale += s.staleCount;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const lines = [];
|
|
138
|
+
|
|
139
|
+
// Frontmatter
|
|
140
|
+
lines.push('---');
|
|
141
|
+
lines.push(`_generator: ${GENERATOR_MARKER}`);
|
|
142
|
+
lines.push(`type: dashboard`);
|
|
143
|
+
lines.push(`created: ${createdValue}`);
|
|
144
|
+
lines.push(`updated: ${updatedValue}`);
|
|
145
|
+
lines.push('---');
|
|
146
|
+
lines.push('');
|
|
147
|
+
|
|
148
|
+
// Title + preamble
|
|
149
|
+
lines.push('# GitLab Portfolio');
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(`> Generated ${formatDateUTC(now)}. Re-run /portfolio to refresh.`);
|
|
152
|
+
lines.push('');
|
|
153
|
+
|
|
154
|
+
// Summary table
|
|
155
|
+
lines.push('## Summary');
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push('| Metric | Count |');
|
|
158
|
+
lines.push('|---|---|');
|
|
159
|
+
lines.push(`| Total repos | ${sortedRepos.length} |`);
|
|
160
|
+
lines.push(`| Total open issues | ${totalOpen} |`);
|
|
161
|
+
lines.push(`| Critical | ${totalCritical} |`);
|
|
162
|
+
lines.push(`| Stale (>${staleDays} days) | ${totalStale} |`);
|
|
163
|
+
lines.push('');
|
|
164
|
+
|
|
165
|
+
// Repos overview table
|
|
166
|
+
lines.push('## Repos');
|
|
167
|
+
lines.push('');
|
|
168
|
+
lines.push('| Repo | Open | Critical | Stale | Last activity | Next milestone |');
|
|
169
|
+
lines.push('|---|---|---|---|---|---|');
|
|
170
|
+
|
|
171
|
+
for (const repo of sortedRepos) {
|
|
172
|
+
const s = summaries.get(repo);
|
|
173
|
+
lines.push(
|
|
174
|
+
`| ${repo} | ${s.openCount} | ${s.criticalCount} | ${s.staleCount} | ${fmtActivity(s.lastActivity)} | ${fmtMilestone(s.nextMilestone)} |`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
lines.push('');
|
|
178
|
+
|
|
179
|
+
// Per-repo detail
|
|
180
|
+
lines.push('## Per-Repo Detail');
|
|
181
|
+
lines.push('');
|
|
182
|
+
|
|
183
|
+
for (const repo of sortedRepos) {
|
|
184
|
+
const s = summaries.get(repo);
|
|
185
|
+
lines.push(`### ${repo}`);
|
|
186
|
+
lines.push('');
|
|
187
|
+
lines.push(`- Last activity: ${fmtActivity(s.lastActivity)}`);
|
|
188
|
+
lines.push(`- Next milestone: ${fmtMilestone(s.nextMilestone)}`);
|
|
189
|
+
|
|
190
|
+
if (s.topThree.length > 0) {
|
|
191
|
+
lines.push('- Top 3 open issues:');
|
|
192
|
+
for (const issue of s.topThree) {
|
|
193
|
+
const labelStr = issue.labels.length > 0 ? `labels: ${issue.labels.join(', ')}` : 'no labels';
|
|
194
|
+
lines.push(` - #${issue.iid} ${issue.title} (${labelStr}) — ${issue.url}`);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
lines.push('- No open issues.');
|
|
198
|
+
}
|
|
199
|
+
lines.push('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── Write (idempotent) ─────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Normalize a markdown string by replacing the `updated:` frontmatter line
|
|
209
|
+
* with a stable placeholder, enabling byte-for-byte noop comparison.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} content
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
function normalizeUpdated(content) {
|
|
215
|
+
// Replace `updated: <anything>` in the frontmatter block with the placeholder.
|
|
216
|
+
// Only replace within the first frontmatter section (between the first --- pair).
|
|
217
|
+
return content.replace(
|
|
218
|
+
/^(updated:\s*)(.+)$/m,
|
|
219
|
+
`$1${UPDATED_PLACEHOLDER}`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Write the portfolio file with idempotency guards.
|
|
225
|
+
*
|
|
226
|
+
* @param {{
|
|
227
|
+
* outputPath: string,
|
|
228
|
+
* content: string,
|
|
229
|
+
* now: Date,
|
|
230
|
+
* dryRun?: boolean,
|
|
231
|
+
* fs?: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
|
|
232
|
+
* }} opts
|
|
233
|
+
* @returns {{ action: 'written'|'skipped-handwritten'|'skipped-noop'|'dry-run', path: string }}
|
|
234
|
+
*/
|
|
235
|
+
export function writePortfolio(opts) {
|
|
236
|
+
const {
|
|
237
|
+
outputPath,
|
|
238
|
+
content,
|
|
239
|
+
dryRun = false,
|
|
240
|
+
fs: injectedFs,
|
|
241
|
+
} = opts;
|
|
242
|
+
|
|
243
|
+
// Resolve fs functions (injectable for tests)
|
|
244
|
+
const fsReadFile = injectedFs?.readFileSync ?? readFileSync;
|
|
245
|
+
const fsWriteFile = injectedFs?.writeFileSync ?? writeFileSync;
|
|
246
|
+
const fsMkdir = injectedFs?.mkdirSync ?? mkdirSync;
|
|
247
|
+
const fsExists = injectedFs?.existsSync ?? existsSync;
|
|
248
|
+
|
|
249
|
+
// Dry-run: never write
|
|
250
|
+
if (dryRun) {
|
|
251
|
+
return { action: 'dry-run', path: outputPath };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (fsExists(outputPath)) {
|
|
255
|
+
let existingContent;
|
|
256
|
+
try {
|
|
257
|
+
existingContent = fsReadFile(outputPath, 'utf8');
|
|
258
|
+
} catch {
|
|
259
|
+
// Cannot read → treat as non-existent, write fresh
|
|
260
|
+
existingContent = null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (existingContent !== null) {
|
|
264
|
+
const fm = parseFrontmatter(existingContent);
|
|
265
|
+
|
|
266
|
+
// Skip-on-manual-edit: no _generator or different generator
|
|
267
|
+
if (!fm || !fm['_generator']) {
|
|
268
|
+
return { action: 'skipped-handwritten', path: outputPath };
|
|
269
|
+
}
|
|
270
|
+
if (fm['_generator'] !== GENERATOR_MARKER) {
|
|
271
|
+
return { action: 'skipped-handwritten', path: outputPath };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Skip-noop: compare content modulo `updated:` timestamp
|
|
275
|
+
const existingNormalized = normalizeUpdated(existingContent);
|
|
276
|
+
const newNormalized = normalizeUpdated(content);
|
|
277
|
+
if (existingNormalized === newNormalized) {
|
|
278
|
+
return { action: 'skipped-noop', path: outputPath };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Write
|
|
284
|
+
const targetDir = dirname(outputPath);
|
|
285
|
+
fsMkdir(targetDir, { recursive: true });
|
|
286
|
+
fsWriteFile(outputPath, content, 'utf8');
|
|
287
|
+
|
|
288
|
+
return { action: 'written', path: outputPath };
|
|
289
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vcs-detect.mjs — VCS auto-detection helpers for the gitlab-portfolio skill.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* discoverVaultRepos — scan vault 01-projects/ for registered repos
|
|
6
|
+
* detectVcsForRepo — resolve 'gitlab' | 'github' | null for a single spec
|
|
7
|
+
*
|
|
8
|
+
* Used by cli.mjs as the discovery step before aggregation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import fsP from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
|
|
15
|
+
import { parseFrontmatter as _parseFrontmatter } from '../vault-mirror/utils.mjs';
|
|
16
|
+
|
|
17
|
+
// ── detectVcsForRepo ──────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the VCS platform for a single repo spec.
|
|
21
|
+
*
|
|
22
|
+
* Priority:
|
|
23
|
+
* 1. Explicit `frontmatter.gitlab` field present → 'gitlab'
|
|
24
|
+
* 2. Explicit `frontmatter.github` field present → 'github'
|
|
25
|
+
* 3. `repo` matches /(?:^|[@/])gitlab\./ or starts with 'gitlab/' → 'gitlab'
|
|
26
|
+
* 4. `repo` contains 'github.com' or starts with 'github/' → 'github'
|
|
27
|
+
* 5. `repo` matches host-less <org>/<repo> shorthand /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/ → 'github' (last resort)
|
|
28
|
+
* 6. Otherwise → null (caller decides)
|
|
29
|
+
*
|
|
30
|
+
* @param {{ frontmatter?: Record<string, string>, repo?: string }} input
|
|
31
|
+
* @returns {'gitlab'|'github'|null}
|
|
32
|
+
*/
|
|
33
|
+
export function detectVcsForRepo(input) {
|
|
34
|
+
const { frontmatter, repo } = input ?? {};
|
|
35
|
+
|
|
36
|
+
// 1. Frontmatter: explicit gitlab field
|
|
37
|
+
if (frontmatter && frontmatter['gitlab']) {
|
|
38
|
+
return 'gitlab';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Frontmatter: explicit github field
|
|
42
|
+
if (frontmatter && frontmatter['github']) {
|
|
43
|
+
return 'github';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (repo && typeof repo === 'string') {
|
|
47
|
+
// 3. Host-pattern: any GitLab instance (hostname starts with 'gitlab.') or group prefix
|
|
48
|
+
if (
|
|
49
|
+
/(?:^|[@/])gitlab\./.test(repo) ||
|
|
50
|
+
repo.startsWith('gitlab/')
|
|
51
|
+
) {
|
|
52
|
+
return 'gitlab';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. Host-pattern: GitHub
|
|
56
|
+
if (repo.includes('github.com') || repo.startsWith('github/')) {
|
|
57
|
+
return 'github';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Shorthand <org>/<repo> with no host marker: assume GitHub (common convention).
|
|
61
|
+
// We only do this as a last resort — explicit frontmatter fields are preferred.
|
|
62
|
+
if (/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
|
|
63
|
+
return 'github';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── discoverVaultRepos ────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Discover registered repos from a vault directory.
|
|
74
|
+
*
|
|
75
|
+
* Scans `<vaultDir>/01-projects/<slug>/_overview.md` frontmatter for `gitlab:`
|
|
76
|
+
* and/or `github:` keys. If BOTH are present, `gitlab` wins (it is the primary
|
|
77
|
+
* VCS; `github` is typically a mirror).
|
|
78
|
+
*
|
|
79
|
+
* Projects without either field are silently skipped. Missing `_overview.md`
|
|
80
|
+
* files are silently skipped.
|
|
81
|
+
*
|
|
82
|
+
* @param {{
|
|
83
|
+
* vaultDir: string,
|
|
84
|
+
* fs?: { readdir: Function, readFile: Function, stat: Function },
|
|
85
|
+
* parseFrontmatter?: (content: string) => Record<string, string> | null,
|
|
86
|
+
* }} opts
|
|
87
|
+
* @returns {Promise<Array<{
|
|
88
|
+
* slug: string,
|
|
89
|
+
* repo: string,
|
|
90
|
+
* vcs: 'gitlab'|'github',
|
|
91
|
+
* overviewPath: string,
|
|
92
|
+
* }>>}
|
|
93
|
+
*/
|
|
94
|
+
export async function discoverVaultRepos(opts) {
|
|
95
|
+
const {
|
|
96
|
+
vaultDir: rawVaultDir,
|
|
97
|
+
fs: injectedFs,
|
|
98
|
+
parseFrontmatter = _parseFrontmatter,
|
|
99
|
+
} = opts;
|
|
100
|
+
|
|
101
|
+
if (!rawVaultDir || typeof rawVaultDir !== 'string') {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Expand ~ if present
|
|
106
|
+
const vaultDir = rawVaultDir.startsWith('~')
|
|
107
|
+
? path.join(os.homedir(), rawVaultDir.slice(1))
|
|
108
|
+
: rawVaultDir;
|
|
109
|
+
|
|
110
|
+
// Resolve fs functions (injectable for tests)
|
|
111
|
+
const fsReaddir = injectedFs?.readdir ?? fsP.readdir;
|
|
112
|
+
const fsReadFile = injectedFs?.readFile ?? fsP.readFile;
|
|
113
|
+
const fsStat = injectedFs?.stat ?? fsP.stat;
|
|
114
|
+
|
|
115
|
+
const projectsDir = path.join(vaultDir, '01-projects');
|
|
116
|
+
|
|
117
|
+
// Read the 01-projects directory
|
|
118
|
+
let entries;
|
|
119
|
+
try {
|
|
120
|
+
entries = await fsReaddir(projectsDir);
|
|
121
|
+
} catch {
|
|
122
|
+
// 01-projects dir does not exist — return empty
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const results = [];
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
// Skip dotfiles
|
|
130
|
+
if (entry.startsWith('.')) continue;
|
|
131
|
+
|
|
132
|
+
const entryPath = path.join(projectsDir, entry);
|
|
133
|
+
|
|
134
|
+
// Skip non-directories
|
|
135
|
+
let stat;
|
|
136
|
+
try {
|
|
137
|
+
stat = await fsStat(entryPath);
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!stat.isDirectory()) continue;
|
|
142
|
+
|
|
143
|
+
const slug = entry;
|
|
144
|
+
const overviewPath = path.join(entryPath, '_overview.md');
|
|
145
|
+
|
|
146
|
+
// Read _overview.md — skip if absent
|
|
147
|
+
let content;
|
|
148
|
+
try {
|
|
149
|
+
content = await fsReadFile(overviewPath, 'utf8');
|
|
150
|
+
} catch {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Parse frontmatter
|
|
155
|
+
const fm = parseFrontmatter(content);
|
|
156
|
+
if (!fm) continue;
|
|
157
|
+
|
|
158
|
+
const gitlabField = fm['gitlab'];
|
|
159
|
+
const githubField = fm['github'];
|
|
160
|
+
|
|
161
|
+
// Determine VCS and repo identifier.
|
|
162
|
+
// Priority: gitlab > github (gitlab is primary; github is typically a mirror).
|
|
163
|
+
if (gitlabField && typeof gitlabField === 'string' && gitlabField.trim()) {
|
|
164
|
+
results.push({
|
|
165
|
+
slug,
|
|
166
|
+
repo: gitlabField.trim(),
|
|
167
|
+
vcs: 'gitlab',
|
|
168
|
+
overviewPath,
|
|
169
|
+
});
|
|
170
|
+
} else if (githubField && typeof githubField === 'string' && githubField.trim()) {
|
|
171
|
+
results.push({
|
|
172
|
+
slug,
|
|
173
|
+
repo: githubField.trim(),
|
|
174
|
+
vcs: 'github',
|
|
175
|
+
overviewPath,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Neither field present → skip silently
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handover-gate.mjs — deterministic routing helper for the /close Handover
|
|
3
|
+
* Alignment Gate (session-end Phase 1.65).
|
|
4
|
+
*
|
|
5
|
+
* PRD: docs/prd/2026-07-07-close-handover-alignment-gate.md
|
|
6
|
+
* Issue: #769 (Epic #724 — Session-Lifecycle & Close-Friction)
|
|
7
|
+
*
|
|
8
|
+
* ## What this module does
|
|
9
|
+
*
|
|
10
|
+
* The gate is skill-prose-first with a minimal mechanical core (same pattern as
|
|
11
|
+
* memory-proposals / phase-skip.mjs): the LLM coordinator runs the AUQ
|
|
12
|
+
* interaction, and this small pure `.mjs` lib makes the deterministic candidate
|
|
13
|
+
* classification trivially testable. There is NO I/O here — no disk, no clock,
|
|
14
|
+
* no randomness — so every function is a pure, deterministic mapping of input
|
|
15
|
+
* records to a routed shape.
|
|
16
|
+
*
|
|
17
|
+
* ## The routing contract (PRD FA2 + §3.A EARS)
|
|
18
|
+
*
|
|
19
|
+
* Every carryover candidate lands in EXACTLY ONE of `autoCarry` or `ask` — no
|
|
20
|
+
* candidate is ever dropped by classification (the Ubiquitous EARS rule). The
|
|
21
|
+
* coordinator later decides the OB (whether an issue is filed) from these two
|
|
22
|
+
* buckets: `autoCarry` items are listed in the gate summary but are NOT
|
|
23
|
+
* user-deselectable; `ask` items become the Middle-Band multiSelect (preselected
|
|
24
|
+
* = carry).
|
|
25
|
+
*
|
|
26
|
+
* A candidate is routed to `autoCarry` when ANY of:
|
|
27
|
+
* - `priority === 'critical'` or `priority === 'high'`, OR
|
|
28
|
+
* - `bucket === 'spiral-failed'` (the SPIRAL/FAILED safety-net, Phase 1.6), OR
|
|
29
|
+
* - `originIssue === null` (Grill decision: dropping a candidate that has no
|
|
30
|
+
* origin issue would be real forgetting — SKILL.md:853 Critical Rule stays
|
|
31
|
+
* intact).
|
|
32
|
+
*
|
|
33
|
+
* A candidate is routed to `ask` when it has an origin issue AND its priority is
|
|
34
|
+
* `medium` / `low` / `null` AND its bucket is `not-started` / `emergent` /
|
|
35
|
+
* `partially-done` (i.e. not high-prio, not spiral-failed).
|
|
36
|
+
*
|
|
37
|
+
* `malformed` records (missing/empty task text) are routed to `ask` with a
|
|
38
|
+
* `malformed: true` flag — the helper NEVER throws on them, and the malformed
|
|
39
|
+
* branch takes precedence over every autoCarry condition (a record with no task
|
|
40
|
+
* text cannot be meaningfully auto-filed, so the operator decides).
|
|
41
|
+
*
|
|
42
|
+
* Empty / non-array input yields `{ autoCarry: [], ask: [] }` — never throws.
|
|
43
|
+
*
|
|
44
|
+
* @typedef {Object} NormalizedCandidate
|
|
45
|
+
* @property {string} task - task text (required; if absent/empty → malformed:true).
|
|
46
|
+
* @property {string} sourcePhase - '1.2' | '1.3' | '1.4' | '1.6' (best-effort string).
|
|
47
|
+
* @property {number|null} originIssue - issue IID, or null when there is no origin issue.
|
|
48
|
+
* @property {'critical'|'high'|'medium'|'low'|null} priority
|
|
49
|
+
* @property {'partially-done'|'not-started'|'emergent'|'spiral-failed'} bucket
|
|
50
|
+
* @property {boolean} [malformed] - true when task text is missing/empty.
|
|
51
|
+
*
|
|
52
|
+
* @typedef {Object} RoutingResult
|
|
53
|
+
* @property {NormalizedCandidate[]} autoCarry - not user-deselectable; gate-summary only.
|
|
54
|
+
* @property {NormalizedCandidate[]} ask - Middle-Band multiSelect (preselected = carry).
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Coercion tables
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/** Valid carryover buckets (PRD §3.A). */
|
|
62
|
+
const VALID_BUCKETS = new Set(['partially-done', 'not-started', 'emergent', 'spiral-failed']);
|
|
63
|
+
|
|
64
|
+
/** Valid priorities (GitLab priority labels, lowercased, label-prefix stripped). */
|
|
65
|
+
const VALID_PRIORITIES = new Set(['critical', 'high', 'medium', 'low']);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Source-phase → bucket inference used when a candidate omits an explicit bucket
|
|
69
|
+
* (PRD §2 In-Scope: 1.2 Partially Done, 1.3 Not Started, 1.4 unfinished Emergent,
|
|
70
|
+
* 1.6 SPIRAL/FAILED walk).
|
|
71
|
+
*/
|
|
72
|
+
const PHASE_BUCKET = {
|
|
73
|
+
'1.2': 'partially-done',
|
|
74
|
+
'1.3': 'not-started',
|
|
75
|
+
'1.4': 'emergent',
|
|
76
|
+
'1.6': 'spiral-failed',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Neutral fallback bucket when neither an explicit bucket nor a known phase maps. */
|
|
80
|
+
const DEFAULT_BUCKET = 'not-started';
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Field coercers (each pure, each defensive)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Coerce an origin-issue field. Numeric string ("769") → number; an integer
|
|
88
|
+
* number passes through; everything else (missing, null, fractional
|
|
89
|
+
* number/string, non-numeric) → null. Issue IIDs are always integers, so a
|
|
90
|
+
* fractional number is coerced identically to a fractional numeric string —
|
|
91
|
+
* both are malformed input and fall through to `null` (routing the candidate
|
|
92
|
+
* to autoCarry via the no-origin rule, the safe direction).
|
|
93
|
+
*
|
|
94
|
+
* @param {unknown} v
|
|
95
|
+
* @returns {number|null}
|
|
96
|
+
*/
|
|
97
|
+
function coerceOriginIssue(v) {
|
|
98
|
+
if (typeof v === 'number') return Number.isInteger(v) ? v : null;
|
|
99
|
+
if (typeof v === 'string') {
|
|
100
|
+
const t = v.trim();
|
|
101
|
+
return /^-?\d+$/.test(t) ? Number(t) : null;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Coerce a priority field: lowercase, trim, strip a leading `priority:` label,
|
|
108
|
+
* validate against the closed enum. Anything invalid → null.
|
|
109
|
+
*
|
|
110
|
+
* @param {unknown} v
|
|
111
|
+
* @returns {'critical'|'high'|'medium'|'low'|null}
|
|
112
|
+
*/
|
|
113
|
+
function coercePriority(v) {
|
|
114
|
+
if (v === null || v === undefined) return null;
|
|
115
|
+
let s = String(v).trim().toLowerCase();
|
|
116
|
+
if (s.startsWith('priority:')) s = s.slice('priority:'.length).trim();
|
|
117
|
+
return VALID_PRIORITIES.has(s) ? /** @type {any} */ (s) : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Coerce the source-phase field to a best-effort trimmed string ('' when absent).
|
|
122
|
+
*
|
|
123
|
+
* @param {unknown} v
|
|
124
|
+
* @returns {string}
|
|
125
|
+
*/
|
|
126
|
+
function coerceSourcePhase(v) {
|
|
127
|
+
if (v === null || v === undefined) return '';
|
|
128
|
+
return String(v).trim();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the bucket: an explicit valid bucket wins; otherwise infer from the
|
|
133
|
+
* source phase; otherwise fall back to the neutral default.
|
|
134
|
+
*
|
|
135
|
+
* @param {unknown} rawBucket
|
|
136
|
+
* @param {string} sourcePhase
|
|
137
|
+
* @returns {'partially-done'|'not-started'|'emergent'|'spiral-failed'}
|
|
138
|
+
*/
|
|
139
|
+
function coerceBucket(rawBucket, sourcePhase) {
|
|
140
|
+
if (rawBucket !== null && rawBucket !== undefined) {
|
|
141
|
+
const b = String(rawBucket).trim().toLowerCase();
|
|
142
|
+
if (VALID_BUCKETS.has(b)) return /** @type {any} */ (b);
|
|
143
|
+
}
|
|
144
|
+
return /** @type {any} */ (PHASE_BUCKET[sourcePhase] ?? DEFAULT_BUCKET);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Public API
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Normalize a raw carryover-candidate record into a {@link NormalizedCandidate}.
|
|
153
|
+
*
|
|
154
|
+
* Accepts any subset of `{ task, sourcePhase, originIssue, priority, bucket }`
|
|
155
|
+
* (strings or numbers) and coerces each field. Missing/empty `task` yields
|
|
156
|
+
* `task: ''` plus `malformed: true` (never throws). Pure and deterministic —
|
|
157
|
+
* no `Date.now()` / `Math.random()`.
|
|
158
|
+
*
|
|
159
|
+
* @param {unknown} raw
|
|
160
|
+
* @returns {NormalizedCandidate}
|
|
161
|
+
*/
|
|
162
|
+
export function normalizeCandidate(raw) {
|
|
163
|
+
const rec = raw && typeof raw === 'object' ? /** @type {Record<string, unknown>} */ (raw) : {};
|
|
164
|
+
|
|
165
|
+
const sourcePhase = coerceSourcePhase(rec.sourcePhase);
|
|
166
|
+
const originIssue = coerceOriginIssue(rec.originIssue);
|
|
167
|
+
const priority = coercePriority(rec.priority);
|
|
168
|
+
const bucket = coerceBucket(rec.bucket, sourcePhase);
|
|
169
|
+
|
|
170
|
+
const hasTask = typeof rec.task === 'string' && rec.task.trim().length > 0;
|
|
171
|
+
|
|
172
|
+
/** @type {NormalizedCandidate} */
|
|
173
|
+
const normalized = {
|
|
174
|
+
task: hasTask ? /** @type {string} */ (rec.task) : '',
|
|
175
|
+
sourcePhase,
|
|
176
|
+
originIssue,
|
|
177
|
+
priority,
|
|
178
|
+
bucket,
|
|
179
|
+
};
|
|
180
|
+
if (!hasTask) normalized.malformed = true;
|
|
181
|
+
|
|
182
|
+
return normalized;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Route a list of raw carryover candidates into `{ autoCarry, ask }`.
|
|
187
|
+
*
|
|
188
|
+
* Each candidate is first normalized via {@link normalizeCandidate}, then
|
|
189
|
+
* classified per the module routing contract. Every candidate lands in exactly
|
|
190
|
+
* one bucket; nothing is dropped. Empty or non-array input → `{ autoCarry: [],
|
|
191
|
+
* ask: [] }`. Never throws.
|
|
192
|
+
*
|
|
193
|
+
* @param {unknown} candidates
|
|
194
|
+
* @returns {RoutingResult}
|
|
195
|
+
*/
|
|
196
|
+
export function routeCandidates(candidates) {
|
|
197
|
+
/** @type {RoutingResult} */
|
|
198
|
+
const result = { autoCarry: [], ask: [] };
|
|
199
|
+
if (!Array.isArray(candidates) || candidates.length === 0) return result;
|
|
200
|
+
|
|
201
|
+
for (const raw of candidates) {
|
|
202
|
+
const c = normalizeCandidate(raw);
|
|
203
|
+
|
|
204
|
+
// malformed takes precedence over every autoCarry condition — a record with
|
|
205
|
+
// no task text cannot be meaningfully auto-filed, so the operator decides.
|
|
206
|
+
if (c.malformed) {
|
|
207
|
+
result.ask.push(c);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const isAutoCarry =
|
|
212
|
+
c.priority === 'critical' ||
|
|
213
|
+
c.priority === 'high' ||
|
|
214
|
+
c.bucket === 'spiral-failed' ||
|
|
215
|
+
c.originIssue === null;
|
|
216
|
+
|
|
217
|
+
if (isAutoCarry) result.autoCarry.push(c);
|
|
218
|
+
else result.ask.push(c);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return result;
|
|
222
|
+
}
|