@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.88
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/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +151 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +298 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4203 -493
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +73 -39
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +288 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +257 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +25 -7
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRD ↔ session review — Pugi final (`/prd-check --session`,
|
|
3
|
+
*).
|
|
4
|
+
*
|
|
5
|
+
* The shipped `/prd-check` surface verifies a PRD's acceptance criteria
|
|
6
|
+
* against committed artifacts on disk (file / test / doc / command /
|
|
7
|
+
* route verifiers — see `core/prd-check/verifiers.ts`). That mode is
|
|
8
|
+
* the canonical CI gate; it inspects a snapshot of the workspace and
|
|
9
|
+
* decides healthy / failing / unparsed.
|
|
10
|
+
*
|
|
11
|
+
* `--session` adds a complementary mode: ask "does the work I just
|
|
12
|
+
* did in this REPL session actually cover the PRD?" without requiring
|
|
13
|
+
* the acceptance criteria to be written in the structured shape the
|
|
14
|
+
* verifier expects. The mode:
|
|
15
|
+
*
|
|
16
|
+
* 1. Walks up from the current working directory looking for
|
|
17
|
+
* `PRD.md`, then `PRODUCT.md`, then `apps/<any>/PRODUCT.md`
|
|
18
|
+
* under the workspace root. The first hit wins; the rest are
|
|
19
|
+
* ignored. We stop at the filesystem root.
|
|
20
|
+
*
|
|
21
|
+
* 2. Reads the last N (default 20) events from `.pugi/events.jsonl`
|
|
22
|
+
* that represent operator-visible turns: user prompts, model
|
|
23
|
+
* responses, and tool calls with their results. Other event
|
|
24
|
+
* types (`session`, `hook`, …) are filtered so the prompt the
|
|
25
|
+
* subagent receives is dominated by intent + work-actually-done
|
|
26
|
+
* rather than infrastructure noise.
|
|
27
|
+
*
|
|
28
|
+
* 3. Builds a structured prompt for a cross-review subagent. The
|
|
29
|
+
* subagent is dispatched via the standard `dispatch()` surface
|
|
30
|
+
* (see `core/subagents/dispatcher.ts`) using the `pm` persona —
|
|
31
|
+
* the closest match to a PRD reviewer in today's roster. The
|
|
32
|
+
* subagent is instructed to return two newline-separated lists:
|
|
33
|
+
* `Satisfied:` and `Outstanding:`.
|
|
34
|
+
*
|
|
35
|
+
* 4. Parses the subagent's response into a structured envelope so
|
|
36
|
+
* the slash + the top-level CLI render the same output shape
|
|
37
|
+
* (Markdown sections in the TUI, JSON for scripts).
|
|
38
|
+
*
|
|
39
|
+
* The module is pure with respect to dependencies — every IO surface
|
|
40
|
+
* (`readPrdSearch`, `readSessionEvents`, `dispatchReview`) is
|
|
41
|
+
* accepted as an injected dep so the spec can drive every branch
|
|
42
|
+
* without a real filesystem or a network call. The default
|
|
43
|
+
* implementations live in `defaultSessionReviewDeps`.
|
|
44
|
+
*
|
|
45
|
+
* The subagent return contract is intentionally loose: any line
|
|
46
|
+
* starting with `Satisfied:` (case-insensitive) opens the satisfied
|
|
47
|
+
* section; any line starting with `Outstanding:` opens the
|
|
48
|
+
* outstanding section. Bulleted lines (`-`, `*`, `•`) inside each
|
|
49
|
+
* section land as list items. Free prose between sections is
|
|
50
|
+
* captured as the `note` field on the envelope so the reviewer can
|
|
51
|
+
* still attach a sentence of context.
|
|
52
|
+
*
|
|
53
|
+
* Cross-review subagent: this module routes to whatever persona
|
|
54
|
+
* resolves the "PRD reviewer" role at dispatch time. In
|
|
55
|
+
* that is the `pm` persona via `dispatch({ role: 'pm', … })`. Once
|
|
56
|
+
* a dedicated reviewer persona lands (project tracking ID:
|
|
57
|
+
* cross-review subagent track), swapping it requires only changing
|
|
58
|
+
* `DEFAULT_REVIEW_ROLE` below.
|
|
59
|
+
*/
|
|
60
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
61
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
62
|
+
/**
|
|
63
|
+
* Names of PRD-style files we will try to locate. Order matters —
|
|
64
|
+
* `PRD.md` wins over `PRODUCT.md` so a project that has both an
|
|
65
|
+
* authored PRD and a legacy PRODUCT brief points at the PRD.
|
|
66
|
+
*/
|
|
67
|
+
const PRD_CANDIDATE_NAMES = ['PRD.md', 'PRODUCT.md'];
|
|
68
|
+
/**
|
|
69
|
+
* Per-app product brief glob. We scan `apps/*` under the workspace
|
|
70
|
+
* root for a top-level `PRODUCT.md` — this matches the layout the
|
|
71
|
+
* Pugi monorepo uses (`apps/admin-api/PRODUCT.md`, etc.). Walk is
|
|
72
|
+
* shallow on purpose to keep the search bounded.
|
|
73
|
+
*/
|
|
74
|
+
const APPS_DIR = 'apps';
|
|
75
|
+
/** Default number of NDJSON turns to surface to the reviewer. */
|
|
76
|
+
export const DEFAULT_TURN_COUNT = 20;
|
|
77
|
+
/**
|
|
78
|
+
* Subagent role we ask to play the PRD reviewer. `reviewer` is the
|
|
79
|
+
* canonical slot in the dispatcher's role enum (see
|
|
80
|
+
* `packages/pugi-sdk/src/subagent-contracts.ts`) — every dispatch
|
|
81
|
+
* resolves a role to a persona at runtime via the isolation matrix.
|
|
82
|
+
* The constant is exported so callers can override (e.g. route to
|
|
83
|
+
* `verifier` for a stricter pass).
|
|
84
|
+
*/
|
|
85
|
+
export const DEFAULT_REVIEW_ROLE = 'reviewer';
|
|
86
|
+
/**
|
|
87
|
+
* Run the `--session` mode end-to-end. The function emits status
|
|
88
|
+
* lines through `deps.onStatus` (the slash dispatcher wires this to
|
|
89
|
+
* `appendSystemLine`) and returns the structured envelope.
|
|
90
|
+
*/
|
|
91
|
+
export async function runSessionReview(workspaceRoot, deps, options = {}) {
|
|
92
|
+
const turnCount = options.turnCount ?? DEFAULT_TURN_COUNT;
|
|
93
|
+
const status = deps.onStatus ?? (() => undefined);
|
|
94
|
+
status('Locating PRD...');
|
|
95
|
+
const prdPath = deps.resolvePrd(workspaceRoot);
|
|
96
|
+
if (prdPath === null) {
|
|
97
|
+
return {
|
|
98
|
+
command: 'prd-check',
|
|
99
|
+
mode: 'session',
|
|
100
|
+
status: 'no_prd',
|
|
101
|
+
prdPath: null,
|
|
102
|
+
prdTitle: null,
|
|
103
|
+
turnsRead: 0,
|
|
104
|
+
satisfied: [],
|
|
105
|
+
outstanding: [],
|
|
106
|
+
note: '',
|
|
107
|
+
rawReview: null,
|
|
108
|
+
reason: `No PRD.md or PRODUCT.md found walking up from ${workspaceRoot}.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const prdSource = safeRead(prdPath);
|
|
112
|
+
if (prdSource === null) {
|
|
113
|
+
return {
|
|
114
|
+
command: 'prd-check',
|
|
115
|
+
mode: 'session',
|
|
116
|
+
status: 'no_prd',
|
|
117
|
+
prdPath: relative(workspaceRoot, prdPath) || prdPath,
|
|
118
|
+
prdTitle: null,
|
|
119
|
+
turnsRead: 0,
|
|
120
|
+
satisfied: [],
|
|
121
|
+
outstanding: [],
|
|
122
|
+
note: '',
|
|
123
|
+
rawReview: null,
|
|
124
|
+
reason: `PRD file at ${prdPath} is unreadable.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const prdTitle = extractTitle(prdSource);
|
|
128
|
+
status(`Reading last ${turnCount} session turns...`);
|
|
129
|
+
const rawEvents = deps.readEventsLog(workspaceRoot);
|
|
130
|
+
const turns = rawEvents === null ? [] : parseTurns(rawEvents, turnCount);
|
|
131
|
+
status('Reviewing against PRD...');
|
|
132
|
+
const prompt = buildReviewPrompt({
|
|
133
|
+
prdPath,
|
|
134
|
+
prdTitle,
|
|
135
|
+
prdSource,
|
|
136
|
+
turns,
|
|
137
|
+
workspaceRoot,
|
|
138
|
+
});
|
|
139
|
+
const outcome = await deps.dispatchReview({
|
|
140
|
+
role: DEFAULT_REVIEW_ROLE,
|
|
141
|
+
prompt,
|
|
142
|
+
workspaceRoot,
|
|
143
|
+
});
|
|
144
|
+
if (!outcome.ok) {
|
|
145
|
+
return {
|
|
146
|
+
command: 'prd-check',
|
|
147
|
+
mode: 'session',
|
|
148
|
+
status: 'dispatch_failed',
|
|
149
|
+
prdPath: relative(workspaceRoot, prdPath) || prdPath,
|
|
150
|
+
prdTitle,
|
|
151
|
+
turnsRead: turns.length,
|
|
152
|
+
satisfied: [],
|
|
153
|
+
outstanding: [],
|
|
154
|
+
note: '',
|
|
155
|
+
rawReview: null,
|
|
156
|
+
reason: outcome.reason,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const parsed = parseReviewResponse(outcome.text);
|
|
160
|
+
return {
|
|
161
|
+
command: 'prd-check',
|
|
162
|
+
mode: 'session',
|
|
163
|
+
status: 'ok',
|
|
164
|
+
prdPath: relative(workspaceRoot, prdPath) || prdPath,
|
|
165
|
+
prdTitle,
|
|
166
|
+
turnsRead: turns.length,
|
|
167
|
+
satisfied: parsed.satisfied,
|
|
168
|
+
outstanding: parsed.outstanding,
|
|
169
|
+
note: parsed.note,
|
|
170
|
+
rawReview: outcome.text,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Render the envelope as plain-text lines for the TUI / shell. The
|
|
175
|
+
* slash + the top-level CLI both use this so the surface stays
|
|
176
|
+
* single-sourced; the structured envelope (above) feeds `--json`.
|
|
177
|
+
*/
|
|
178
|
+
export function renderSessionReview(result) {
|
|
179
|
+
if (result.status === 'no_prd') {
|
|
180
|
+
return result.reason ?? 'No PRD found.';
|
|
181
|
+
}
|
|
182
|
+
if (result.status === 'dispatch_failed') {
|
|
183
|
+
return `PRD review dispatch failed: ${result.reason ?? 'unknown'}`;
|
|
184
|
+
}
|
|
185
|
+
const lines = [];
|
|
186
|
+
lines.push(`PRD review (${result.turnsRead} turn(s) read from .pugi/events.jsonl)`);
|
|
187
|
+
if (result.prdPath !== null) {
|
|
188
|
+
const titlePart = result.prdTitle === null ? '' : ` — ${result.prdTitle}`;
|
|
189
|
+
lines.push(`Source: ${result.prdPath}${titlePart}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push('');
|
|
192
|
+
if (result.satisfied.length === 0) {
|
|
193
|
+
lines.push('Satisfied: (none reported)');
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
lines.push('Satisfied:');
|
|
197
|
+
for (const item of result.satisfied)
|
|
198
|
+
lines.push(` - ${item}`);
|
|
199
|
+
}
|
|
200
|
+
lines.push('');
|
|
201
|
+
if (result.outstanding.length === 0) {
|
|
202
|
+
lines.push('Outstanding: (none reported)');
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
lines.push('Outstanding:');
|
|
206
|
+
for (const item of result.outstanding)
|
|
207
|
+
lines.push(` - ${item}`);
|
|
208
|
+
}
|
|
209
|
+
if (result.note.length > 0) {
|
|
210
|
+
lines.push('');
|
|
211
|
+
lines.push(`Note: ${result.note}`);
|
|
212
|
+
}
|
|
213
|
+
return lines.join('\n');
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Compose the prompt handed to the reviewer subagent. The prompt is
|
|
217
|
+
* deterministic — input order matters for prompt cache hits so we
|
|
218
|
+
* keep the layout stable: PRD source first, session turns second,
|
|
219
|
+
* forcing-question schema last.
|
|
220
|
+
*/
|
|
221
|
+
export function buildReviewPrompt(input) {
|
|
222
|
+
const parts = [];
|
|
223
|
+
parts.push('You are a PRD reviewer. Read the PRD below, then read the recent session turns, then decide which requirements are SATISFIED by the work-so-far and which remain OUTSTANDING.');
|
|
224
|
+
parts.push('');
|
|
225
|
+
parts.push('Reply in EXACTLY this shape:');
|
|
226
|
+
parts.push('');
|
|
227
|
+
parts.push('Satisfied:');
|
|
228
|
+
parts.push('- <requirement>');
|
|
229
|
+
parts.push('- <requirement>');
|
|
230
|
+
parts.push('');
|
|
231
|
+
parts.push('Outstanding:');
|
|
232
|
+
parts.push('- <requirement>');
|
|
233
|
+
parts.push('- <requirement>');
|
|
234
|
+
parts.push('');
|
|
235
|
+
parts.push('Each list item is one PRD requirement, paraphrased to fit a single line. Do not invent requirements that are not in the PRD. If the session shows no work toward a requirement, list it Outstanding.');
|
|
236
|
+
parts.push('');
|
|
237
|
+
parts.push('--- PRD start ---');
|
|
238
|
+
parts.push(`Path: ${relative(input.workspaceRoot, input.prdPath) || input.prdPath}`);
|
|
239
|
+
if (input.prdTitle !== null)
|
|
240
|
+
parts.push(`Title: ${input.prdTitle}`);
|
|
241
|
+
parts.push('');
|
|
242
|
+
parts.push(input.prdSource);
|
|
243
|
+
parts.push('--- PRD end ---');
|
|
244
|
+
parts.push('');
|
|
245
|
+
parts.push(`--- Last ${input.turns.length} session turn(s) start ---`);
|
|
246
|
+
for (const turn of input.turns) {
|
|
247
|
+
parts.push(formatTurn(turn));
|
|
248
|
+
}
|
|
249
|
+
parts.push('--- session turns end ---');
|
|
250
|
+
return parts.join('\n');
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Walk up from cwd looking for the first PRD / PRODUCT file. Also
|
|
254
|
+
* scans `apps/*` under the workspace root once we find a directory
|
|
255
|
+
* that contains an `apps` folder. Returns absolute path or null.
|
|
256
|
+
*/
|
|
257
|
+
export function defaultResolvePrd(cwd) {
|
|
258
|
+
let current = resolve(cwd);
|
|
259
|
+
for (let i = 0; i < 32; i += 1) {
|
|
260
|
+
for (const name of PRD_CANDIDATE_NAMES) {
|
|
261
|
+
const candidate = join(current, name);
|
|
262
|
+
if (existsSync(candidate) && safeIsFile(candidate)) {
|
|
263
|
+
return candidate;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// If this directory holds an apps/ folder, scan one level deep
|
|
267
|
+
// for app-scoped PRODUCT.md. We do NOT keep walking up past this
|
|
268
|
+
// point — the apps/ folder marks the workspace root in our
|
|
269
|
+
// monorepo layout.
|
|
270
|
+
const appsDir = join(current, APPS_DIR);
|
|
271
|
+
if (existsSync(appsDir) && safeIsDirectory(appsDir)) {
|
|
272
|
+
const appHit = scanAppsForProduct(appsDir);
|
|
273
|
+
if (appHit !== null)
|
|
274
|
+
return appHit;
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const parent = dirname(current);
|
|
278
|
+
if (parent === current)
|
|
279
|
+
break;
|
|
280
|
+
current = parent;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Default events log reader. Returns the raw file contents or null
|
|
286
|
+
* when the log is absent — `.pugi/events.jsonl` only exists when
|
|
287
|
+
* the workspace has had at least one session.
|
|
288
|
+
*/
|
|
289
|
+
export function defaultReadEventsLog(workspaceRoot) {
|
|
290
|
+
const path = join(workspaceRoot, '.pugi', 'events.jsonl');
|
|
291
|
+
return safeRead(path);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Default cross-review dispatcher. Routes through the standard
|
|
295
|
+
* subagents `dispatch()` surface in stub-mode (no engine client) so
|
|
296
|
+
* the slash + the CLI top-level have a working default that does not
|
|
297
|
+
* require a live admin-api connection. When an engine client is in
|
|
298
|
+
* scope (callers supply one via injection from the runtime cli.ts
|
|
299
|
+
* layer once auth is resolved), `dispatch()` will route to the real
|
|
300
|
+
* cross-review subagent.
|
|
301
|
+
*
|
|
302
|
+
* Imported dynamically so test paths that inject a stub never pull
|
|
303
|
+
* the dispatcher graph into the spec.
|
|
304
|
+
*/
|
|
305
|
+
export const defaultDispatchReview = async (input) => {
|
|
306
|
+
try {
|
|
307
|
+
const { dispatch, inMemoryDispatcherContext } = await import('../subagents/dispatcher.js');
|
|
308
|
+
// The dispatcher's role enum is the canonical schema; `as` keeps
|
|
309
|
+
// the caller's role string flowing through without re-importing
|
|
310
|
+
// the schema here (the schema parse inside `dispatch()` will
|
|
311
|
+
// reject if it does not match — that is the contract gate).
|
|
312
|
+
const task = {
|
|
313
|
+
id: `prd-review-${Date.now()}`,
|
|
314
|
+
role: input.role,
|
|
315
|
+
prompt: input.prompt,
|
|
316
|
+
permissionMode: 'plan',
|
|
317
|
+
};
|
|
318
|
+
const ctx = inMemoryDispatcherContext({
|
|
319
|
+
sessionId: `prd-review-session-${Date.now()}`,
|
|
320
|
+
workspaceRoot: input.workspaceRoot,
|
|
321
|
+
sink: [],
|
|
322
|
+
});
|
|
323
|
+
const outcome = await dispatch(task, ctx);
|
|
324
|
+
if (!outcome || typeof outcome !== 'object') {
|
|
325
|
+
return { ok: false, reason: 'dispatcher returned an unexpected shape' };
|
|
326
|
+
}
|
|
327
|
+
if (typeof outcome.summary === 'string' && outcome.summary.length > 0) {
|
|
328
|
+
return { ok: true, text: outcome.summary };
|
|
329
|
+
}
|
|
330
|
+
return { ok: false, reason: 'dispatcher response carried no reviewable summary' };
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
334
|
+
return { ok: false, reason: message };
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
/** Convenience bundle for callers that want the real-IO defaults. */
|
|
338
|
+
export function defaultSessionReviewDeps(overrides = {}) {
|
|
339
|
+
return {
|
|
340
|
+
resolvePrd: overrides.resolvePrd ?? defaultResolvePrd,
|
|
341
|
+
readEventsLog: overrides.readEventsLog ?? defaultReadEventsLog,
|
|
342
|
+
dispatchReview: overrides.dispatchReview ?? defaultDispatchReview,
|
|
343
|
+
...(overrides.onStatus !== undefined ? { onStatus: overrides.onStatus } : {}),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Parse NDJSON, filter to operator-visible turns, and return the
|
|
348
|
+
* last N in chronological order. Malformed lines are silently
|
|
349
|
+
* dropped — the events log is appended to from multiple processes
|
|
350
|
+
* and partial writes during shutdown can leave a trailing line.
|
|
351
|
+
*/
|
|
352
|
+
export function parseTurns(raw, max) {
|
|
353
|
+
const lines = raw.split('\n').filter((line) => line.trim().length > 0);
|
|
354
|
+
const events = [];
|
|
355
|
+
for (const line of lines) {
|
|
356
|
+
try {
|
|
357
|
+
const parsed = JSON.parse(line);
|
|
358
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
359
|
+
events.push(parsed);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// Drop malformed lines.
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const visible = [];
|
|
367
|
+
for (const event of events) {
|
|
368
|
+
const role = classifyEvent(event);
|
|
369
|
+
if (role === null)
|
|
370
|
+
continue;
|
|
371
|
+
visible.push({
|
|
372
|
+
index: visible.length,
|
|
373
|
+
role,
|
|
374
|
+
summary: summariseEvent(event, role),
|
|
375
|
+
raw: event,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (visible.length <= max) {
|
|
379
|
+
return visible.map((t, idx) => ({ ...t, index: idx }));
|
|
380
|
+
}
|
|
381
|
+
return visible.slice(visible.length - max).map((t, idx) => ({ ...t, index: idx }));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Pull a "Satisfied:" / "Outstanding:" pair out of the subagent text.
|
|
385
|
+
* Both labels are matched case-insensitively at line start. Bulleted
|
|
386
|
+
* items use any of `- `, `* `, or `• `. Free prose before / between
|
|
387
|
+
* sections lands in `note` (concatenated with a single space so the
|
|
388
|
+
* renderer can decide on wrapping).
|
|
389
|
+
*/
|
|
390
|
+
export function parseReviewResponse(text) {
|
|
391
|
+
const lines = text.split('\n');
|
|
392
|
+
const satisfied = [];
|
|
393
|
+
const outstanding = [];
|
|
394
|
+
const noteParts = [];
|
|
395
|
+
let section = 'none';
|
|
396
|
+
for (const raw of lines) {
|
|
397
|
+
const line = raw.trim();
|
|
398
|
+
if (line.length === 0)
|
|
399
|
+
continue;
|
|
400
|
+
const header = matchSectionHeader(line);
|
|
401
|
+
if (header !== null) {
|
|
402
|
+
section = header;
|
|
403
|
+
// Capture any inline item after the colon, e.g. "Satisfied: foo".
|
|
404
|
+
const inline = stripHeaderPrefix(line);
|
|
405
|
+
if (inline.length > 0) {
|
|
406
|
+
pushItem(inline, section, satisfied, outstanding);
|
|
407
|
+
}
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const bulleted = matchBulleted(line);
|
|
411
|
+
if (bulleted !== null) {
|
|
412
|
+
pushItem(bulleted, section, satisfied, outstanding);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (section === 'none') {
|
|
416
|
+
noteParts.push(line);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
// Continuation of the previous bulleted item — append.
|
|
420
|
+
const target = section === 'satisfied' ? satisfied : outstanding;
|
|
421
|
+
if (target.length > 0) {
|
|
422
|
+
target[target.length - 1] = `${target[target.length - 1]} ${line}`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
noteParts.push(line);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return { satisfied, outstanding, note: noteParts.join(' ') };
|
|
430
|
+
}
|
|
431
|
+
/* ------------------------------ helpers ------------------------------ */
|
|
432
|
+
function safeRead(path) {
|
|
433
|
+
try {
|
|
434
|
+
return readFileSync(path, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function safeIsFile(path) {
|
|
441
|
+
try {
|
|
442
|
+
return statSync(path).isFile();
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function safeIsDirectory(path) {
|
|
449
|
+
try {
|
|
450
|
+
return statSync(path).isDirectory();
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function scanAppsForProduct(appsDir) {
|
|
457
|
+
let entries;
|
|
458
|
+
try {
|
|
459
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
460
|
+
entries = require('node:fs').readdirSync(appsDir);
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
entries.sort();
|
|
466
|
+
for (const entry of entries) {
|
|
467
|
+
const candidate = join(appsDir, entry, 'PRODUCT.md');
|
|
468
|
+
if (existsSync(candidate) && safeIsFile(candidate))
|
|
469
|
+
return candidate;
|
|
470
|
+
const prdCandidate = join(appsDir, entry, 'PRD.md');
|
|
471
|
+
if (existsSync(prdCandidate) && safeIsFile(prdCandidate))
|
|
472
|
+
return prdCandidate;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
function extractTitle(source) {
|
|
477
|
+
for (const raw of source.split('\n')) {
|
|
478
|
+
const line = raw.trim();
|
|
479
|
+
if (line.length === 0)
|
|
480
|
+
continue;
|
|
481
|
+
if (line.startsWith('# '))
|
|
482
|
+
return line.slice(2).trim();
|
|
483
|
+
if (line.startsWith('#'))
|
|
484
|
+
return line.replace(/^#+/, '').trim();
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
function classifyEvent(event) {
|
|
490
|
+
const type = typeof event.type === 'string' ? event.type : '';
|
|
491
|
+
if (type === 'user' || type === 'user_message' || type === 'prompt')
|
|
492
|
+
return 'user';
|
|
493
|
+
if (type === 'assistant' || type === 'model' || type === 'response' || type === 'completion') {
|
|
494
|
+
return 'model';
|
|
495
|
+
}
|
|
496
|
+
if (type === 'tool_call' || type === 'tool_result' || type === 'file_mutation')
|
|
497
|
+
return 'tool';
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function summariseEvent(event, role) {
|
|
501
|
+
const text = typeof event.text === 'string' ? event.text : '';
|
|
502
|
+
const tool = typeof event.tool === 'string' ? event.tool : '';
|
|
503
|
+
const status = typeof event.status === 'string' ? event.status : '';
|
|
504
|
+
const path = typeof event.path === 'string' ? event.path : '';
|
|
505
|
+
const op = typeof event.operation === 'string' ? event.operation : '';
|
|
506
|
+
switch (role) {
|
|
507
|
+
case 'user':
|
|
508
|
+
return clip(text, 480);
|
|
509
|
+
case 'model':
|
|
510
|
+
return clip(text, 800);
|
|
511
|
+
case 'tool': {
|
|
512
|
+
if (event.type === 'tool_call')
|
|
513
|
+
return `tool_call ${tool || '?'}`;
|
|
514
|
+
if (event.type === 'tool_result')
|
|
515
|
+
return `tool_result ${tool || '?'} ${status}`.trim();
|
|
516
|
+
if (event.type === 'file_mutation')
|
|
517
|
+
return `file_mutation ${op} ${path}`.trim();
|
|
518
|
+
return `tool ${tool}`;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function clip(text, max) {
|
|
523
|
+
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
524
|
+
if (oneLine.length <= max)
|
|
525
|
+
return oneLine;
|
|
526
|
+
return `${oneLine.slice(0, max - 1)}…`;
|
|
527
|
+
}
|
|
528
|
+
function formatTurn(turn) {
|
|
529
|
+
return `[${turn.index + 1}] ${turn.role}: ${turn.summary}`;
|
|
530
|
+
}
|
|
531
|
+
function matchSectionHeader(line) {
|
|
532
|
+
const lower = line.toLowerCase();
|
|
533
|
+
if (/^satisfied\b/.test(lower))
|
|
534
|
+
return 'satisfied';
|
|
535
|
+
if (/^outstanding\b/.test(lower))
|
|
536
|
+
return 'outstanding';
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
function stripHeaderPrefix(line) {
|
|
540
|
+
const colon = line.indexOf(':');
|
|
541
|
+
if (colon === -1)
|
|
542
|
+
return '';
|
|
543
|
+
return line.slice(colon + 1).trim();
|
|
544
|
+
}
|
|
545
|
+
function matchBulleted(line) {
|
|
546
|
+
const match = /^(?:[-*•]|\d+\.)\s+(.*)$/u.exec(line);
|
|
547
|
+
return match === null ? null : (match[1] ?? '').trim();
|
|
548
|
+
}
|
|
549
|
+
function pushItem(item, section, satisfied, outstanding) {
|
|
550
|
+
if (item.length === 0)
|
|
551
|
+
return;
|
|
552
|
+
if (section === 'satisfied')
|
|
553
|
+
satisfied.push(item);
|
|
554
|
+
else if (section === 'outstanding')
|
|
555
|
+
outstanding.push(item);
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=session-review.js.map
|