@pugi/cli 0.1.0-beta.98 → 1.0.0-alpha.1
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/LICENSE +1 -1
- package/README.md +11 -191
- package/bin/pugi +8 -0
- package/package.json +15 -71
- package/postinstall.mjs +31 -0
- package/CHANGELOG.md +0 -132
- package/THIRD_PARTY_NOTICES.md +0 -40
- package/assets/pugi-mascot.ansi +0 -16
- package/assets/pugi-prozr2-mascot.ansi +0 -9
- package/bin/run.js +0 -34
- package/dist/commands/deploy.js +0 -439
- package/dist/commands/flatten.js +0 -191
- package/dist/commands/jobs-watch.js +0 -201
- package/dist/commands/jobs.js +0 -260
- package/dist/commands/retro.js +0 -210
- package/dist/commands/smoke.js +0 -133
- package/dist/core/agent-progress/cleanup.js +0 -134
- package/dist/core/agent-progress/schema.js +0 -144
- package/dist/core/agent-progress/writer.js +0 -101
- package/dist/core/agents/adaptive-router.js +0 -330
- package/dist/core/agents/loader.js +0 -104
- package/dist/core/agents/query-decomposer.js +0 -297
- package/dist/core/agents/registry.js +0 -69
- package/dist/core/approvals/shortcut-resolver.js +0 -98
- package/dist/core/artifact-chain/dispatcher.js +0 -148
- package/dist/core/artifact-chain/exporter.js +0 -164
- package/dist/core/artifact-chain/state.js +0 -243
- package/dist/core/artifact-chain/steps.js +0 -169
- package/dist/core/ask-user/question.js +0 -92
- package/dist/core/audit/audit-trail.js +0 -275
- package/dist/core/auth/ensure-authenticated.js +0 -129
- package/dist/core/auth/env-provider.js +0 -238
- package/dist/core/auto-open-browser.js +0 -128
- package/dist/core/auto-update/channels.js +0 -122
- package/dist/core/auto-update/checker.js +0 -241
- package/dist/core/auto-update/state.js +0 -235
- package/dist/core/bare-mode/index.js +0 -107
- package/dist/core/bash/redirect.js +0 -281
- package/dist/core/bash-classifier.js +0 -1397
- package/dist/core/checkpoint/resumer.js +0 -149
- package/dist/core/checkpoint/rewinder.js +0 -291
- package/dist/core/checkpoints/shadow-git.js +0 -670
- package/dist/core/citations/parser.js +0 -109
- package/dist/core/classifier/yolo-classifier.js +0 -88
- package/dist/core/clipboard.js +0 -70
- package/dist/core/codegraph/decision-store.js +0 -248
- package/dist/core/codegraph/detect-repo.js +0 -459
- package/dist/core/codegraph/install.js +0 -134
- package/dist/core/codegraph/offer-hook.js +0 -220
- package/dist/core/compact/auto-trigger.js +0 -96
- package/dist/core/compact/buffer-rewriter.js +0 -115
- package/dist/core/compact/summarizer.js +0 -208
- package/dist/core/compact/token-counter.js +0 -108
- package/dist/core/consensus/anvil-fanout.js +0 -276
- package/dist/core/consensus/diff-capture.js +0 -491
- package/dist/core/consensus/rubric.js +0 -233
- package/dist/core/context/builder.js +0 -114
- package/dist/core/context/compaction-events.js +0 -99
- package/dist/core/context/compaction.js +0 -602
- package/dist/core/context/index.js +0 -28
- package/dist/core/context/invariants.js +0 -250
- package/dist/core/context/markdown-loader.js +0 -288
- package/dist/core/context/markdown-traverse.js +0 -255
- package/dist/core/context/pugiignore.js +0 -316
- package/dist/core/context/repo-skeleton.js +0 -533
- package/dist/core/context/tool-eviction.js +0 -55
- package/dist/core/context/watcher.js +0 -342
- package/dist/core/context/working-set.js +0 -165
- package/dist/core/coordinator/agent-tools.js +0 -77
- package/dist/core/coordinator/agent-toolset.js +0 -65
- package/dist/core/coordinator/fsm.js +0 -73
- package/dist/core/coordinator/mode-fsm.js +0 -70
- package/dist/core/cost/rate-card.js +0 -129
- package/dist/core/cost/tracker.js +0 -221
- package/dist/core/credentials.js +0 -355
- package/dist/core/cron/scheduler.js +0 -138
- package/dist/core/denial-tracking/index.js +0 -8
- package/dist/core/denial-tracking/state.js +0 -264
- package/dist/core/diagnostics/probe-runner.js +0 -93
- package/dist/core/diagnostics/probes/api.js +0 -46
- package/dist/core/diagnostics/probes/auth.js +0 -93
- package/dist/core/diagnostics/probes/bare-mode.js +0 -42
- package/dist/core/diagnostics/probes/cli-version.js +0 -127
- package/dist/core/diagnostics/probes/config.js +0 -72
- package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
- package/dist/core/diagnostics/probes/disk.js +0 -81
- package/dist/core/diagnostics/probes/engine-live.js +0 -46
- package/dist/core/diagnostics/probes/git.js +0 -65
- package/dist/core/diagnostics/probes/hooks.js +0 -118
- package/dist/core/diagnostics/probes/mcp.js +0 -75
- package/dist/core/diagnostics/probes/node.js +0 -59
- package/dist/core/diagnostics/probes/pnpm.js +0 -36
- package/dist/core/diagnostics/probes/pugi-md.js +0 -89
- package/dist/core/diagnostics/probes/sandbox.js +0 -72
- package/dist/core/diagnostics/probes/session.js +0 -74
- package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
- package/dist/core/diagnostics/probes/workspace.js +0 -63
- package/dist/core/diagnostics/types.js +0 -70
- package/dist/core/dispatch/cache-cleanup.js +0 -197
- package/dist/core/dispatch/cache-handoff.js +0 -295
- package/dist/core/edits/apply-patch-layer-e.js +0 -189
- package/dist/core/edits/dispatch.js +0 -511
- package/dist/core/edits/format-detector.js +0 -260
- package/dist/core/edits/format-matrix.js +0 -26
- package/dist/core/edits/fuzzy-ladder.js +0 -650
- package/dist/core/edits/index.js +0 -19
- package/dist/core/edits/journal.js +0 -199
- package/dist/core/edits/layer-a-apply.js +0 -217
- package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
- package/dist/core/edits/layer-b-apply.js +0 -211
- package/dist/core/edits/layer-c-apply.js +0 -160
- package/dist/core/edits/layer-d-ast.js +0 -572
- package/dist/core/edits/marker-parser.js +0 -401
- package/dist/core/edits/security-gate.js +0 -223
- package/dist/core/edits/verify-hook.js +0 -273
- package/dist/core/edits/worktree.js +0 -322
- package/dist/core/engine/adapter-runner.js +0 -8
- package/dist/core/engine/anvil-client.js +0 -344
- package/dist/core/engine/auto-compact.js +0 -179
- package/dist/core/engine/budgets.js +0 -192
- package/dist/core/engine/context-prefix.js +0 -155
- package/dist/core/engine/index.js +0 -12
- package/dist/core/engine/intensity.js +0 -163
- package/dist/core/engine/intent.js +0 -260
- package/dist/core/engine/native-pugi.js +0 -1616
- package/dist/core/engine/noop.js +0 -27
- package/dist/core/engine/prompts.js +0 -236
- package/dist/core/engine/strip-internal-fields.js +0 -124
- package/dist/core/engine/tool-bridge.js +0 -2173
- package/dist/core/engine/verification-patterns.js +0 -195
- package/dist/core/evaluation/golden-dataset.js +0 -293
- package/dist/core/feedback/queue.js +0 -177
- package/dist/core/feedback/submitter.js +0 -145
- package/dist/core/file-cache.js +0 -141
- package/dist/core/flatten/flatten-repo.js +0 -439
- package/dist/core/format/osc8-link.js +0 -28
- package/dist/core/hook-chains.js +0 -392
- package/dist/core/hooks/citation-verify-hook.js +0 -138
- package/dist/core/hooks/citation-verify.js +0 -112
- package/dist/core/hooks/events.js +0 -46
- package/dist/core/hooks/index.js +0 -15
- package/dist/core/hooks/registry.js +0 -216
- package/dist/core/hooks/runner.js +0 -236
- package/dist/core/hooks/v2/event-emitter.js +0 -115
- package/dist/core/hooks/v2/executor.js +0 -282
- package/dist/core/hooks/v2/index.js +0 -25
- package/dist/core/hooks/v2/lifecycle.js +0 -104
- package/dist/core/hooks/v2/loader.js +0 -216
- package/dist/core/hooks/v2/matcher.js +0 -125
- package/dist/core/hooks/v2/trust.js +0 -143
- package/dist/core/hooks/v2/types.js +0 -86
- package/dist/core/hooks/worktree-events.js +0 -158
- package/dist/core/hooks.js +0 -415
- package/dist/core/image/renderer.js +0 -71
- package/dist/core/index-store.js +0 -260
- package/dist/core/init/detector.js +0 -582
- package/dist/core/init/template-renderer.js +0 -242
- package/dist/core/jobs/registry.js +0 -462
- package/dist/core/ledger/results-tsv.js +0 -142
- package/dist/core/log-discipline/stdout-redirect.js +0 -51
- package/dist/core/lsp/cache.js +0 -105
- package/dist/core/lsp/client.js +0 -1229
- package/dist/core/lsp/language-detect.js +0 -66
- package/dist/core/lsp/post-edit-diagnostics.js +0 -171
- package/dist/core/lsp/server-detect.js +0 -173
- package/dist/core/lsp/symbol-cache.js +0 -162
- package/dist/core/lsp/symbol-tools.js +0 -664
- package/dist/core/mcp/client.js +0 -385
- package/dist/core/mcp/http-server.js +0 -553
- package/dist/core/mcp/orchestrator-config.js +0 -192
- package/dist/core/mcp/orchestrator-tools.js +0 -806
- package/dist/core/mcp/permission.js +0 -190
- package/dist/core/mcp/registry.js +0 -193
- package/dist/core/mcp/server-tools.js +0 -219
- package/dist/core/mcp/server.js +0 -397
- package/dist/core/mcp/trust.js +0 -91
- package/dist/core/memory/dual-write.js +0 -416
- package/dist/core/memory/passive-extract.js +0 -130
- package/dist/core/memory/phase1-kinds.js +0 -20
- package/dist/core/memory/secret-scanner.js +0 -304
- package/dist/core/memory-sync/queue.js +0 -170
- package/dist/core/metrics/extract.js +0 -113
- package/dist/core/modes/roo-modes.js +0 -68
- package/dist/core/onboarding/ensure-initialized.js +0 -133
- package/dist/core/onboarding/marker.js +0 -111
- package/dist/core/onboarding/telemetry-state.js +0 -108
- package/dist/core/output-style/presets.js +0 -176
- package/dist/core/output-style/state.js +0 -185
- package/dist/core/path-security.js +0 -345
- package/dist/core/permission.js +0 -369
- package/dist/core/permissions/auto-classifier.js +0 -124
- package/dist/core/permissions/bash-parser.js +0 -371
- package/dist/core/permissions/circuit-breaker.js +0 -83
- package/dist/core/permissions/constrained-edit.js +0 -91
- package/dist/core/permissions/gate.js +0 -278
- package/dist/core/permissions/index.js +0 -20
- package/dist/core/permissions/mode.js +0 -174
- package/dist/core/permissions/network-egress.js +0 -137
- package/dist/core/permissions/state.js +0 -241
- package/dist/core/permissions/tool-class.js +0 -107
- package/dist/core/plan-mode/ui-state.js +0 -51
- package/dist/core/plans/plan-artifact.js +0 -721
- package/dist/core/policy-limits/etag-store.js +0 -122
- package/dist/core/prd-check/parser.js +0 -215
- package/dist/core/prd-check/reporter.js +0 -127
- package/dist/core/prd-check/session-review.js +0 -557
- package/dist/core/prd-check/verifiers.js +0 -223
- package/dist/core/prompt-cache/client-cache.js +0 -99
- package/dist/core/prompts/assembly.js +0 -29
- package/dist/core/prompts/registry.js +0 -364
- package/dist/core/pugi-gitignore.js +0 -52
- package/dist/core/pugi-md/cc-compat-rules.js +0 -735
- package/dist/core/pugi-md/context-injector.js +0 -76
- package/dist/core/pugi-md/walk-up.js +0 -207
- package/dist/core/python/uv-installer.js +0 -270
- package/dist/core/python/uv-resolver.js +0 -83
- package/dist/core/rate-limit/narrator.js +0 -146
- package/dist/core/recipes/cli-types.js +0 -20
- package/dist/core/recipes/loader.js +0 -103
- package/dist/core/recipes/runner.js +0 -345
- package/dist/core/recipes/schema.js +0 -587
- package/dist/core/release-notes/parser.js +0 -241
- package/dist/core/release-notes/state.js +0 -116
- package/dist/core/repl/ask.js +0 -512
- package/dist/core/repl/cancellation.js +0 -98
- package/dist/core/repl/cap-warning.js +0 -91
- package/dist/core/repl/clipboard-read.js +0 -174
- package/dist/core/repl/dispatch-fsm.js +0 -220
- package/dist/core/repl/engine-bridge.js +0 -303
- package/dist/core/repl/history-search.js +0 -175
- package/dist/core/repl/history.js +0 -182
- package/dist/core/repl/kill-ring.js +0 -138
- package/dist/core/repl/model-pricing.js +0 -135
- package/dist/core/repl/privacy-banner.js +0 -71
- package/dist/core/repl/session.js +0 -4962
- package/dist/core/repl/slash-commands.js +0 -747
- package/dist/core/repl/store/index.js +0 -12
- package/dist/core/repl/store/jsonl-log.js +0 -321
- package/dist/core/repl/store/lockfile.js +0 -155
- package/dist/core/repl/store/session-store.js +0 -821
- package/dist/core/repl/store/types.js +0 -44
- package/dist/core/repl/store/uuid-v7.js +0 -68
- package/dist/core/repl/tool-route.js +0 -382
- package/dist/core/repl/workspace-context.js +0 -206
- package/dist/core/repo-map/build.js +0 -125
- package/dist/core/repo-map/cache.js +0 -185
- package/dist/core/repo-map/extractor.js +0 -254
- package/dist/core/repo-map/formatter.js +0 -145
- package/dist/core/repo-map/page-rank.js +0 -105
- package/dist/core/repo-map/scanner.js +0 -211
- package/dist/core/retro/git-collector.js +0 -251
- package/dist/core/retro/health-card.js +0 -25
- package/dist/core/retro/metrics.js +0 -342
- package/dist/core/retro/narrative.js +0 -249
- package/dist/core/retro/plane-collector.js +0 -274
- package/dist/core/retro/pr-issue-link.js +0 -65
- package/dist/core/retro/types.js +0 -16
- package/dist/core/retry-budget/budget.js +0 -284
- package/dist/core/retry-budget/index.js +0 -5
- package/dist/core/retry-budget/retry-cap.js +0 -74
- package/dist/core/routing/lead-worker.js +0 -43
- package/dist/core/routing/pre-flight-estimator.js +0 -108
- package/dist/core/runs/run-tree.js +0 -103
- package/dist/core/sandboxing/adapter.js +0 -29
- package/dist/core/sandboxing/index.js +0 -49
- package/dist/core/sandboxing/none.js +0 -19
- package/dist/core/sandboxing/seatbelt.js +0 -183
- package/dist/core/security/injection-scanner.js +0 -367
- package/dist/core/security/output-filter.js +0 -418
- package/dist/core/session/env-file.js +0 -105
- package/dist/core/session/section-budgets.js +0 -140
- package/dist/core/session.js +0 -377
- package/dist/core/settings.js +0 -400
- package/dist/core/share/formatter.js +0 -271
- package/dist/core/share/redactor.js +0 -221
- package/dist/core/share/uploader.js +0 -267
- package/dist/core/skills/defaults.js +0 -457
- package/dist/core/skills/loader.js +0 -454
- package/dist/core/skills/sources.js +0 -480
- package/dist/core/skills/trust.js +0 -172
- package/dist/core/smoke/headless-driver.js +0 -174
- package/dist/core/smoke/orchestrator.js +0 -194
- package/dist/core/smoke/runner.js +0 -238
- package/dist/core/smoke/scenario-parser.js +0 -316
- package/dist/core/statusline.js +0 -99
- package/dist/core/subagents/dispatcher-real.js +0 -600
- package/dist/core/subagents/dispatcher.js +0 -352
- package/dist/core/subagents/index.js +0 -39
- package/dist/core/subagents/isolation-matrix.js +0 -213
- package/dist/core/subagents/spawn.js +0 -101
- package/dist/core/telemetry/emitter.js +0 -229
- package/dist/core/telemetry/queue.js +0 -251
- package/dist/core/theme/context.js +0 -91
- package/dist/core/theme/presets.js +0 -228
- package/dist/core/theme/state.js +0 -181
- package/dist/core/todos/invariant.js +0 -10
- package/dist/core/todos/state.js +0 -177
- package/dist/core/tool-schema/compressor.js +0 -89
- package/dist/core/transport/version-interceptor.js +0 -166
- package/dist/core/trust.js +0 -109
- package/dist/core/tui/thinking-block.js +0 -64
- package/dist/core/vim/keymap.js +0 -288
- package/dist/core/vim/state.js +0 -92
- package/dist/core/watch-markers/marker-watcher.js +0 -133
- package/dist/core/worktree/include-parser.js +0 -249
- package/dist/core/worktree-manager/cleanup.js +0 -123
- package/dist/core/worktree-manager/manager.js +0 -303
- package/dist/index.js +0 -44
- package/dist/runtime/bootstrap.js +0 -190
- package/dist/runtime/cli.js +0 -8121
- package/dist/runtime/commands/agents.js +0 -385
- package/dist/runtime/commands/budget.js +0 -192
- package/dist/runtime/commands/cancel.js +0 -231
- package/dist/runtime/commands/chain.js +0 -489
- package/dist/runtime/commands/codegraph-status.js +0 -227
- package/dist/runtime/commands/compact.js +0 -297
- package/dist/runtime/commands/config.js +0 -595
- package/dist/runtime/commands/cost.js +0 -199
- package/dist/runtime/commands/delegate.js +0 -312
- package/dist/runtime/commands/dispatch.js +0 -126
- package/dist/runtime/commands/doctor.js +0 -579
- package/dist/runtime/commands/feedback.js +0 -184
- package/dist/runtime/commands/hooks.js +0 -187
- package/dist/runtime/commands/init.js +0 -254
- package/dist/runtime/commands/lsp.js +0 -368
- package/dist/runtime/commands/mcp.js +0 -935
- package/dist/runtime/commands/memory.js +0 -582
- package/dist/runtime/commands/model.js +0 -237
- package/dist/runtime/commands/onboarding.js +0 -275
- package/dist/runtime/commands/patch.js +0 -128
- package/dist/runtime/commands/permissions.js +0 -112
- package/dist/runtime/commands/plan.js +0 -143
- package/dist/runtime/commands/prd-check.js +0 -285
- package/dist/runtime/commands/privacy.js +0 -107
- package/dist/runtime/commands/recipe.js +0 -325
- package/dist/runtime/commands/redo-blob-store.js +0 -92
- package/dist/runtime/commands/redo.js +0 -361
- package/dist/runtime/commands/release-notes.js +0 -229
- package/dist/runtime/commands/repo-map.js +0 -95
- package/dist/runtime/commands/report.js +0 -299
- package/dist/runtime/commands/resume.js +0 -118
- package/dist/runtime/commands/review-consensus.js +0 -414
- package/dist/runtime/commands/rewind.js +0 -333
- package/dist/runtime/commands/roster.js +0 -117
- package/dist/runtime/commands/sessions.js +0 -163
- package/dist/runtime/commands/share.js +0 -316
- package/dist/runtime/commands/skills.js +0 -401
- package/dist/runtime/commands/status.js +0 -186
- package/dist/runtime/commands/stickers.js +0 -82
- package/dist/runtime/commands/style.js +0 -194
- package/dist/runtime/commands/theme.js +0 -196
- package/dist/runtime/commands/undo.js +0 -361
- package/dist/runtime/commands/update.js +0 -289
- package/dist/runtime/commands/vim.js +0 -140
- package/dist/runtime/commands/worktree.js +0 -177
- package/dist/runtime/commands/worktrees.js +0 -155
- package/dist/runtime/deprecation-warning.js +0 -69
- package/dist/runtime/engine-exit-code.js +0 -50
- package/dist/runtime/headless-repl.js +0 -195
- package/dist/runtime/headless.js +0 -548
- package/dist/runtime/load-hooks-or-exit.js +0 -71
- package/dist/runtime/plan-decompose.js +0 -531
- package/dist/runtime/sigint-guard.js +0 -272
- package/dist/runtime/stream-renderer.js +0 -195
- package/dist/runtime/update-check.js +0 -294
- package/dist/runtime/version.js +0 -65
- package/dist/runtime/worktree-bootstrap.js +0 -579
- package/dist/skills/bundled/batch.js +0 -617
- package/dist/skills/bundled/index.js +0 -45
- package/dist/skills/bundled/loop.js +0 -358
- package/dist/skills/bundled/remember.js +0 -383
- package/dist/skills/bundled/simplify.js +0 -289
- package/dist/skills/bundled/skillify.js +0 -373
- package/dist/skills/bundled/stuck.js +0 -558
- package/dist/skills/bundled/verify.js +0 -439
- package/dist/testing/vcr.js +0 -486
- package/dist/tools/agent-tool.js +0 -229
- package/dist/tools/apply-patch.js +0 -556
- package/dist/tools/ask-user-question.js +0 -337
- package/dist/tools/ask-user.js +0 -115
- package/dist/tools/bash.js +0 -1238
- package/dist/tools/brief.js +0 -224
- package/dist/tools/cron.js +0 -433
- package/dist/tools/enter-worktree.js +0 -250
- package/dist/tools/exit-worktree.js +0 -147
- package/dist/tools/file-tools.js +0 -553
- package/dist/tools/http-request.js +0 -336
- package/dist/tools/lsp-tools.js +0 -565
- package/dist/tools/mcp-tool.js +0 -260
- package/dist/tools/multi-edit.js +0 -361
- package/dist/tools/powershell.js +0 -268
- package/dist/tools/registry.js +0 -166
- package/dist/tools/server-tools.js +0 -892
- package/dist/tools/skill-tool.js +0 -96
- package/dist/tools/sleep.js +0 -99
- package/dist/tools/synthetic-output.js +0 -133
- package/dist/tools/tasks.js +0 -208
- package/dist/tools/todo-write.js +0 -184
- package/dist/tools/verify-plan-execution.js +0 -295
- package/dist/tools/web-fetch-injection-scanner.js +0 -207
- package/dist/tools/web-fetch.js +0 -720
- package/dist/tools/web-search.js +0 -458
- package/dist/tui/agent-progress-card.js +0 -111
- package/dist/tui/agent-tree-pane.js +0 -9
- package/dist/tui/agent-tree.js +0 -87
- package/dist/tui/ask-cli.js +0 -52
- package/dist/tui/ask-modal.js +0 -211
- package/dist/tui/ask-user-question-chips.js +0 -315
- package/dist/tui/ask-user-question-prompt.js +0 -203
- package/dist/tui/compact-banner.js +0 -81
- package/dist/tui/conversation-pane.js +0 -164
- package/dist/tui/cost-table.js +0 -111
- package/dist/tui/device-flow.js +0 -142
- package/dist/tui/doctor-table.js +0 -46
- package/dist/tui/feedback-prompt.js +0 -156
- package/dist/tui/input-box.js +0 -732
- package/dist/tui/login-picker.js +0 -69
- package/dist/tui/markdown-render.js +0 -266
- package/dist/tui/multi-file-diff-approval.js +0 -375
- package/dist/tui/onboarding-wizard.js +0 -240
- package/dist/tui/permissions-picker.js +0 -86
- package/dist/tui/render.js +0 -160
- package/dist/tui/repl-render.js +0 -770
- package/dist/tui/repl-splash-art.js +0 -64
- package/dist/tui/repl-splash-mascot.js +0 -154
- package/dist/tui/repl-splash.js +0 -117
- package/dist/tui/repl.js +0 -378
- package/dist/tui/slash-palette.js +0 -106
- package/dist/tui/splash-data.js +0 -61
- package/dist/tui/splash.js +0 -31
- package/dist/tui/status-bar.js +0 -209
- package/dist/tui/status-table.js +0 -7
- package/dist/tui/stickers-art.js +0 -136
- package/dist/tui/style-table.js +0 -28
- package/dist/tui/theme-table.js +0 -29
- package/dist/tui/thinking-spinner.js +0 -123
- package/dist/tui/tool-stream-pane.js +0 -140
- package/dist/tui/update-banner.js +0 -33
- package/dist/tui/vim-input.js +0 -267
- package/dist/tui/welcome-banner.js +0 -107
- package/dist/tui/welcome-data.js +0 -293
- package/dist/tui/workspace-context.js +0 -105
- package/docs/examples/codegraph.mcp.json +0 -10
- package/test/scenarios/codegen-create-file.scenario.txt +0 -13
- package/test/scenarios/compact-force.scenario.txt +0 -12
- package/test/scenarios/identity.scenario.txt +0 -11
- package/test/scenarios/persona-handoff.scenario.txt +0 -12
- package/test/scenarios/walkback.scenario.txt +0 -12
|
@@ -1,531 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import { relative, resolve, sep } from 'node:path';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
/**
|
|
5
|
-
* `pugi plan --decompose <idea>` — pattern ported (not copied) from
|
|
6
|
-
* the piercelamb/deep-project plugin (MIT-licensed, 134 stars). The Deep
|
|
7
|
-
* Trilogy splits a high-level idea into focused components, each backed
|
|
8
|
-
* by its own `spec.md`, plus a `project-manifest.md` with the dependency
|
|
9
|
-
* DAG. Pugi's port reuses our existing `pugi plan` engine task and
|
|
10
|
-
* persists artifacts under `.pugi/plan/<session-id>/` so the decomposed
|
|
11
|
-
* specs are reviewable + resumable in the same shape as the rest of the
|
|
12
|
-
* artifact store.
|
|
13
|
-
*
|
|
14
|
-
* Why a separate module: the engine task in `runtime/cli.ts` is already
|
|
15
|
-
* 200+ LOC. Lifting the prompt suffix, JSON parser, and atomic writer
|
|
16
|
-
* out keeps the integration in `runEngineTask` to a single import + a
|
|
17
|
-
* single post-result branch. The module exports a small surface
|
|
18
|
-
* (parser + writer + helpers) so the spec can drive each piece in
|
|
19
|
-
* isolation against golden fixtures.
|
|
20
|
-
*
|
|
21
|
-
* Reference: https://github.com/piercelamb/deep-project (MIT). Pugi
|
|
22
|
-
* extracted the high-level pattern (split-into-components + manifest
|
|
23
|
-
* DAG + per-component spec) and ships a Pugi-native implementation.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Suffix appended to the operator's `pugi plan --decompose` prompt so
|
|
27
|
-
* the model emits a single JSON block at the end of its final answer.
|
|
28
|
-
* The JSON is the machine-readable contract; the prose preceding it
|
|
29
|
-
* (rationale, alternatives considered) lands inside `manifest.md` for
|
|
30
|
-
* the operator's review.
|
|
31
|
-
*
|
|
32
|
-
* Design choices anchored by the Deep-Project pattern + our own
|
|
33
|
-
* planning ergonomics:
|
|
34
|
-
* - 3-7 component sweet spot: more than 7 produces unreviewable
|
|
35
|
-
* spaghetti, fewer than 3 wastes the decomposition step. The
|
|
36
|
-
* schema enforces this hard so the prompt promise matches the
|
|
37
|
-
* contract.
|
|
38
|
-
* - `dependsOn` references component names verbatim — the writer
|
|
39
|
-
* enforces uniqueness and resolves the DAG, so a typo there
|
|
40
|
-
* fails loud at parse time rather than producing a silently
|
|
41
|
-
* wrong manifest.
|
|
42
|
-
* - JSON fence is mandatory so the parser can `slice` it out
|
|
43
|
-
* deterministically; loose JSON-in-prose extractions are
|
|
44
|
-
* brittle and trigger false positives.
|
|
45
|
-
*/
|
|
46
|
-
export const DECOMPOSE_PROMPT_SUFFIX = [
|
|
47
|
-
'',
|
|
48
|
-
'## Decomposition request',
|
|
49
|
-
'',
|
|
50
|
-
'Split the idea above into 3-7 focused components. Each component must be:',
|
|
51
|
-
'- Small enough to fit a single supervised build session (~half a day).',
|
|
52
|
-
'- Explicit about its dependencies on other components by name.',
|
|
53
|
-
'- Specified well enough that a fresh `pugi plan <component-name>` session could pick it up.',
|
|
54
|
-
'',
|
|
55
|
-
'After your prose rationale, emit a SINGLE fenced JSON block at the END of your answer.',
|
|
56
|
-
'The JSON block MUST match this shape exactly:',
|
|
57
|
-
'',
|
|
58
|
-
'```json',
|
|
59
|
-
'{',
|
|
60
|
-
' "components": [',
|
|
61
|
-
' {',
|
|
62
|
-
' "name": "kebab-case-name",',
|
|
63
|
-
' "summary": "one-sentence headline",',
|
|
64
|
-
' "spec": "multi-line spec body — what to build, acceptance criteria, files touched",',
|
|
65
|
-
' "dependsOn": ["other-component-name", "..."]',
|
|
66
|
-
' }',
|
|
67
|
-
' ]',
|
|
68
|
-
'}',
|
|
69
|
-
'```',
|
|
70
|
-
'',
|
|
71
|
-
'Constraints:',
|
|
72
|
-
'- `name` is kebab-case, 1-48 chars, unique across components.',
|
|
73
|
-
'- `dependsOn` references other component names from the same list. Empty array if no dependencies.',
|
|
74
|
-
'- Order components in topological order (dependencies first).',
|
|
75
|
-
'- Do NOT emit additional fields. Do NOT emit multiple JSON blocks. Do NOT inline implementation code.',
|
|
76
|
-
].join('\n');
|
|
77
|
-
/**
|
|
78
|
-
* Windows reserved filename basenames (case-insensitive). The atomic
|
|
79
|
-
* writer rejects any component name that lowercases to one of these so
|
|
80
|
-
* a future Windows checkout never trips on `splits/01-con/spec.md` etc.
|
|
81
|
-
* The kebab-case regex already blocks the colon-separated `com1:` style,
|
|
82
|
-
* but the bare `con` / `nul` / `prn` / `aux` / `comN` / `lptN` forms
|
|
83
|
-
* still pass the regex; the denylist closes that gap.
|
|
84
|
-
*/
|
|
85
|
-
const WINDOWS_RESERVED_BASENAMES = new Set([
|
|
86
|
-
'con',
|
|
87
|
-
'prn',
|
|
88
|
-
'aux',
|
|
89
|
-
'nul',
|
|
90
|
-
'com1',
|
|
91
|
-
'com2',
|
|
92
|
-
'com3',
|
|
93
|
-
'com4',
|
|
94
|
-
'com5',
|
|
95
|
-
'com6',
|
|
96
|
-
'com7',
|
|
97
|
-
'com8',
|
|
98
|
-
'com9',
|
|
99
|
-
'lpt1',
|
|
100
|
-
'lpt2',
|
|
101
|
-
'lpt3',
|
|
102
|
-
'lpt4',
|
|
103
|
-
'lpt5',
|
|
104
|
-
'lpt6',
|
|
105
|
-
'lpt7',
|
|
106
|
-
'lpt8',
|
|
107
|
-
'lpt9',
|
|
108
|
-
]);
|
|
109
|
-
/**
|
|
110
|
-
* Reject any free-text field whose body would close a triple-backtick
|
|
111
|
-
* fence and re-open the surrounding Markdown to model-controlled
|
|
112
|
-
* interpretation. Mermaid blocks and the SPLIT_MANIFEST JSON block are
|
|
113
|
-
* both rendered inside ``` fences in the manifest, so a stray ``` in
|
|
114
|
-
* `summary` / `prompt` / `name` / `spec` would tip the renderer mid
|
|
115
|
-
* fence. We reject at the schema layer (fail loud) rather than escape
|
|
116
|
-
* at render time — escaping would still let model-authored text leak
|
|
117
|
-
* into the Mermaid AST and we have no need to round-trip backticks.
|
|
118
|
-
*/
|
|
119
|
-
const TRIPLE_BACKTICK_RE = /```/;
|
|
120
|
-
/**
|
|
121
|
-
* Single decomposed component. The shape mirrors the JSON contract in
|
|
122
|
-
* the prompt suffix exactly so the parser is a one-shot `z.parse`.
|
|
123
|
-
*/
|
|
124
|
-
const componentSchema = z.object({
|
|
125
|
-
name: z
|
|
126
|
-
.string()
|
|
127
|
-
.min(1)
|
|
128
|
-
.max(48)
|
|
129
|
-
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, 'name must be kebab-case (a-z, 0-9, hyphen)')
|
|
130
|
-
.refine((value) => !TRIPLE_BACKTICK_RE.test(value), {
|
|
131
|
-
message: 'name must not contain triple backticks',
|
|
132
|
-
})
|
|
133
|
-
.refine((value) => !WINDOWS_RESERVED_BASENAMES.has(value.toLowerCase()), {
|
|
134
|
-
message: 'name collides with a Windows reserved filename',
|
|
135
|
-
}),
|
|
136
|
-
summary: z
|
|
137
|
-
.string()
|
|
138
|
-
.min(1)
|
|
139
|
-
.max(200)
|
|
140
|
-
.refine((value) => !TRIPLE_BACKTICK_RE.test(value), {
|
|
141
|
-
message: 'summary must not contain triple backticks',
|
|
142
|
-
}),
|
|
143
|
-
spec: z
|
|
144
|
-
.string()
|
|
145
|
-
.min(1)
|
|
146
|
-
.refine((value) => !TRIPLE_BACKTICK_RE.test(value), {
|
|
147
|
-
message: 'spec must not contain triple backticks',
|
|
148
|
-
}),
|
|
149
|
-
// Reject empty strings at the array element level so a stray `""` in
|
|
150
|
-
// `dependsOn` cannot pass the structural validation and turn into a
|
|
151
|
-
// mystery node id later.
|
|
152
|
-
dependsOn: z.array(z.string().min(1)).default([]),
|
|
153
|
-
});
|
|
154
|
-
export const decompositionSchema = z
|
|
155
|
-
.object({
|
|
156
|
-
// 3-7 components matches the prompt contract verbatim. Tighter
|
|
157
|
-
// bounds catch malformed outputs (e.g. a model that returned a
|
|
158
|
-
// single mega-component or a list of fifteen) at parse time.
|
|
159
|
-
components: z.array(componentSchema).min(3).max(7),
|
|
160
|
-
})
|
|
161
|
-
.superRefine((value, ctx) => {
|
|
162
|
-
const names = new Set();
|
|
163
|
-
for (const [index, component] of value.components.entries()) {
|
|
164
|
-
if (names.has(component.name)) {
|
|
165
|
-
ctx.addIssue({
|
|
166
|
-
code: z.ZodIssueCode.custom,
|
|
167
|
-
path: ['components', index, 'name'],
|
|
168
|
-
message: `duplicate component name "${component.name}"`,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
names.add(component.name);
|
|
172
|
-
}
|
|
173
|
-
for (const [index, component] of value.components.entries()) {
|
|
174
|
-
for (const [depIndex, dep] of component.dependsOn.entries()) {
|
|
175
|
-
if (!names.has(dep)) {
|
|
176
|
-
// Surface the unresolved reference at parse time so the writer
|
|
177
|
-
// is guaranteed a referentially-closed DAG.
|
|
178
|
-
ctx.addIssue({
|
|
179
|
-
code: z.ZodIssueCode.custom,
|
|
180
|
-
path: ['components', index, 'dependsOn', depIndex],
|
|
181
|
-
message: `dependsOn references unknown component "${dep}"`,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (dep === component.name) {
|
|
185
|
-
ctx.addIssue({
|
|
186
|
-
code: z.ZodIssueCode.custom,
|
|
187
|
-
path: ['components', index, 'dependsOn', depIndex],
|
|
188
|
-
message: `component "${component.name}" cannot depend on itself`,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
/**
|
|
195
|
-
* Extract the LAST fenced JSON block from the model's final text and
|
|
196
|
-
* validate it against the decomposition schema. The LAST block is the
|
|
197
|
-
* right choice because models sometimes emit example JSON inside the
|
|
198
|
-
* rationale to illustrate a point, then emit the canonical block at
|
|
199
|
-
* the bottom. Honouring "last" makes the contract unambiguous.
|
|
200
|
-
*
|
|
201
|
-
* Fence patterns accepted: ```json ... ``` (preferred) and bare
|
|
202
|
-
* ``` ... ``` when the body is valid JSON. We do NOT support naked
|
|
203
|
-
* JSON outside a fence: that is brittle (any model that wraps prose
|
|
204
|
-
* in quotes would tip the parser) and the prompt mandates the fence.
|
|
205
|
-
*
|
|
206
|
-
* Line endings: the regex tolerates both LF and CRLF newlines so a
|
|
207
|
-
* Windows-checkout transcript pastes through without ceremony.
|
|
208
|
-
*/
|
|
209
|
-
export function parseDecompositionFromText(text) {
|
|
210
|
-
const blocks = extractFencedJsonBlocks(text);
|
|
211
|
-
if (blocks.length === 0) {
|
|
212
|
-
return {
|
|
213
|
-
ok: false,
|
|
214
|
-
reason: 'no_json_block',
|
|
215
|
-
detail: 'model did not emit a fenced JSON block',
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
// Pick the LAST block — see docstring. The rationale is everything
|
|
219
|
-
// before that block's opening fence.
|
|
220
|
-
const last = blocks[blocks.length - 1];
|
|
221
|
-
const rationale = text.slice(0, last.fenceStart).trim();
|
|
222
|
-
let parsed;
|
|
223
|
-
try {
|
|
224
|
-
parsed = JSON.parse(last.body);
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
return {
|
|
228
|
-
ok: false,
|
|
229
|
-
reason: 'invalid_json',
|
|
230
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
const result = decompositionSchema.safeParse(parsed);
|
|
234
|
-
if (!result.success) {
|
|
235
|
-
return {
|
|
236
|
-
ok: false,
|
|
237
|
-
reason: 'invalid_schema',
|
|
238
|
-
detail: result.error.issues
|
|
239
|
-
.map((issue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`)
|
|
240
|
-
.join('; '),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
return { ok: true, decomposition: result.data, rationale };
|
|
244
|
-
}
|
|
245
|
-
function extractFencedJsonBlocks(text) {
|
|
246
|
-
const blocks = [];
|
|
247
|
-
// Matches ```json\n...\n``` and ```\n...\n``` (capture group 1 is body).
|
|
248
|
-
// We use `[\s\S]*?` (non-greedy) so adjacent fences do not merge into a
|
|
249
|
-
// single capture. The `\r?\n` tolerance keeps CRLF transcripts working
|
|
250
|
-
// without a separate normalization pass.
|
|
251
|
-
const pattern = /```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n```/g;
|
|
252
|
-
let match = pattern.exec(text);
|
|
253
|
-
while (match !== null) {
|
|
254
|
-
blocks.push({
|
|
255
|
-
fenceStart: match.index,
|
|
256
|
-
fenceEnd: match.index + match[0].length,
|
|
257
|
-
body: match[1].trim(),
|
|
258
|
-
});
|
|
259
|
-
match = pattern.exec(text);
|
|
260
|
-
}
|
|
261
|
-
return blocks;
|
|
262
|
-
}
|
|
263
|
-
export function writeDecomposition(input) {
|
|
264
|
-
const { root, sessionId, prompt, decomposition, rationale } = input;
|
|
265
|
-
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
266
|
-
const planDir = resolve(root, '.pugi', 'plan', sessionId);
|
|
267
|
-
const splitsDir = resolve(planDir, 'splits');
|
|
268
|
-
const stagingSuffix = `splits.tmp-${sessionId}-${process.pid}-${Date.now()}`;
|
|
269
|
-
const stagingDir = resolve(planDir, stagingSuffix);
|
|
270
|
-
const manifestFinalPath = resolve(planDir, 'manifest.md');
|
|
271
|
-
const manifestTmpPath = `${manifestFinalPath}.tmp-${process.pid}-${Date.now()}`;
|
|
272
|
-
mkdirSync(planDir, { recursive: true });
|
|
273
|
-
mkdirSync(stagingDir, { recursive: true });
|
|
274
|
-
const splitPaths = [];
|
|
275
|
-
try {
|
|
276
|
-
for (const [index, component] of decomposition.components.entries()) {
|
|
277
|
-
const numbered = `${String(index + 1).padStart(2, '0')}-${component.name}`;
|
|
278
|
-
const componentDir = resolve(stagingDir, numbered);
|
|
279
|
-
// Defense-in-depth: even though the schema rejects `..` / `/` /
|
|
280
|
-
// NUL / Windows reserved basenames in `name`, re-assert that the
|
|
281
|
-
// resolved path stays inside the staging directory before any
|
|
282
|
-
// byte is written. One regex change away from escape becomes one
|
|
283
|
-
// assertion away from escape.
|
|
284
|
-
assertContained(stagingDir, componentDir);
|
|
285
|
-
mkdirSync(componentDir, { recursive: true });
|
|
286
|
-
const specPath = resolve(componentDir, 'spec.md');
|
|
287
|
-
assertContained(stagingDir, specPath);
|
|
288
|
-
writeFileSync(specPath, formatSpec(component, decomposition, generatedAt), {
|
|
289
|
-
encoding: 'utf8',
|
|
290
|
-
mode: 0o600,
|
|
291
|
-
});
|
|
292
|
-
splitPaths.push({
|
|
293
|
-
name: numbered,
|
|
294
|
-
// The relative path is reported against the FINAL splits dir so
|
|
295
|
-
// operator-facing output never mentions the staging prefix.
|
|
296
|
-
path: relative(root, resolve(splitsDir, numbered, 'spec.md')),
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
// Manifest staged next to its final path so a single rename swaps
|
|
300
|
-
// it into place. Same atomicity argument as the splits tree.
|
|
301
|
-
writeFileSync(manifestTmpPath, formatManifest({
|
|
302
|
-
prompt,
|
|
303
|
-
decomposition,
|
|
304
|
-
rationale,
|
|
305
|
-
generatedAt,
|
|
306
|
-
sessionId,
|
|
307
|
-
}), { encoding: 'utf8', mode: 0o600 });
|
|
308
|
-
// Final flip — both renames happen after every byte is on disk.
|
|
309
|
-
// `splits/` is published first because the manifest body references
|
|
310
|
-
// it; that ordering means a reader who sees the manifest can trust
|
|
311
|
-
// every path inside it resolves.
|
|
312
|
-
renameSync(stagingDir, splitsDir);
|
|
313
|
-
renameSync(manifestTmpPath, manifestFinalPath);
|
|
314
|
-
}
|
|
315
|
-
catch (error) {
|
|
316
|
-
// Best-effort cleanup of staged artifacts so a failed run does not
|
|
317
|
-
// leave a `splits.tmp-...` carcass behind. We swallow cleanup
|
|
318
|
-
// errors to keep the original failure as the surfaced cause.
|
|
319
|
-
try {
|
|
320
|
-
rmSync(stagingDir, { recursive: true, force: true });
|
|
321
|
-
}
|
|
322
|
-
catch {
|
|
323
|
-
// ignore
|
|
324
|
-
}
|
|
325
|
-
try {
|
|
326
|
-
rmSync(manifestTmpPath, { force: true });
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
// ignore
|
|
330
|
-
}
|
|
331
|
-
throw error;
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
planDir,
|
|
335
|
-
manifestPath: manifestFinalPath,
|
|
336
|
-
splitPaths,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Assert that `candidate` (already absolute via `resolve`) lives inside
|
|
341
|
-
* `parent`. Catches the unlikely-but-possible case where the kebab
|
|
342
|
-
* regex is relaxed in a future PR and a `..` slips through.
|
|
343
|
-
*/
|
|
344
|
-
function assertContained(parent, candidate) {
|
|
345
|
-
const parentWithSep = parent.endsWith(sep) ? parent : `${parent}${sep}`;
|
|
346
|
-
if (candidate !== parent && !candidate.startsWith(parentWithSep)) {
|
|
347
|
-
throw new Error(`decomposition path escape detected: ${candidate} is not inside ${parent}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Render the per-component `spec.md`. Sections kept narrow so the file
|
|
352
|
-
* is grep-friendly and the next `pugi plan` session can pick it up
|
|
353
|
-
* with minimal context loading.
|
|
354
|
-
*/
|
|
355
|
-
export function formatSpec(component, decomposition, generatedAt) {
|
|
356
|
-
const dependants = decomposition.components
|
|
357
|
-
.filter((other) => other.dependsOn.includes(component.name))
|
|
358
|
-
.map((other) => other.name);
|
|
359
|
-
const lines = [];
|
|
360
|
-
lines.push(`# ${component.name}`);
|
|
361
|
-
lines.push('');
|
|
362
|
-
lines.push(`> ${component.summary}`);
|
|
363
|
-
lines.push('');
|
|
364
|
-
lines.push(`**Generated:** ${generatedAt}`);
|
|
365
|
-
lines.push('');
|
|
366
|
-
lines.push('## Depends on');
|
|
367
|
-
lines.push('');
|
|
368
|
-
if (component.dependsOn.length === 0) {
|
|
369
|
-
lines.push('_(none — this component can start in parallel with other roots)_');
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
for (const dep of component.dependsOn)
|
|
373
|
-
lines.push(`- ${dep}`);
|
|
374
|
-
}
|
|
375
|
-
lines.push('');
|
|
376
|
-
lines.push('## Blocked by this component');
|
|
377
|
-
lines.push('');
|
|
378
|
-
if (dependants.length === 0) {
|
|
379
|
-
lines.push('_(none — this is a leaf component)_');
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
for (const dep of dependants)
|
|
383
|
-
lines.push(`- ${dep}`);
|
|
384
|
-
}
|
|
385
|
-
lines.push('');
|
|
386
|
-
lines.push('## Spec');
|
|
387
|
-
lines.push('');
|
|
388
|
-
lines.push(component.spec.trim());
|
|
389
|
-
lines.push('');
|
|
390
|
-
return lines.join('\n');
|
|
391
|
-
}
|
|
392
|
-
export function formatManifest(input) {
|
|
393
|
-
const { prompt, decomposition, rationale, generatedAt, sessionId } = input;
|
|
394
|
-
const lines = [];
|
|
395
|
-
lines.push('# Pugi Plan — Decomposition Manifest');
|
|
396
|
-
lines.push('');
|
|
397
|
-
lines.push(`**Prompt:** ${prompt}`);
|
|
398
|
-
lines.push(`**Session:** ${sessionId}`);
|
|
399
|
-
lines.push(`**Generated:** ${generatedAt}`);
|
|
400
|
-
lines.push(`**Component count:** ${decomposition.components.length}`);
|
|
401
|
-
lines.push('');
|
|
402
|
-
if (rationale) {
|
|
403
|
-
lines.push('## Rationale');
|
|
404
|
-
lines.push('');
|
|
405
|
-
lines.push(rationale);
|
|
406
|
-
lines.push('');
|
|
407
|
-
}
|
|
408
|
-
lines.push('## Components');
|
|
409
|
-
lines.push('');
|
|
410
|
-
for (const [index, component] of decomposition.components.entries()) {
|
|
411
|
-
const numbered = `${String(index + 1).padStart(2, '0')}-${component.name}`;
|
|
412
|
-
const deps = component.dependsOn.length === 0 ? '_(no deps)_' : component.dependsOn.join(', ');
|
|
413
|
-
lines.push(`- **${numbered}** — ${component.summary} (depends on: ${deps})`);
|
|
414
|
-
}
|
|
415
|
-
lines.push('');
|
|
416
|
-
lines.push('## Dependency DAG');
|
|
417
|
-
lines.push('');
|
|
418
|
-
lines.push('```mermaid');
|
|
419
|
-
lines.push('graph TD');
|
|
420
|
-
for (const component of decomposition.components) {
|
|
421
|
-
const safeNode = sanitizeMermaidId(component.name);
|
|
422
|
-
lines.push(` ${safeNode}["${component.name}"]`);
|
|
423
|
-
}
|
|
424
|
-
for (const component of decomposition.components) {
|
|
425
|
-
const toNode = sanitizeMermaidId(component.name);
|
|
426
|
-
for (const dep of component.dependsOn) {
|
|
427
|
-
const fromNode = sanitizeMermaidId(dep);
|
|
428
|
-
lines.push(` ${fromNode} --> ${toNode}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
lines.push('```');
|
|
432
|
-
lines.push('');
|
|
433
|
-
lines.push('## Execution order');
|
|
434
|
-
lines.push('');
|
|
435
|
-
const topo = topologicalOrder(decomposition);
|
|
436
|
-
const order = topo.order;
|
|
437
|
-
for (const [index, name] of order.entries()) {
|
|
438
|
-
lines.push(`${index + 1}. ${name}`);
|
|
439
|
-
}
|
|
440
|
-
lines.push('');
|
|
441
|
-
if (topo.cycleNodes.length > 0) {
|
|
442
|
-
// Cycle detected — surface explicitly so the operator does not have
|
|
443
|
-
// to count "fewer lines than components" by hand. We do not refuse
|
|
444
|
-
// to render the manifest: it still has every spec link plus the
|
|
445
|
-
// mermaid graph, which actually visualises the cycle.
|
|
446
|
-
lines.push('## Cycles detected');
|
|
447
|
-
lines.push('');
|
|
448
|
-
lines.push(`The dependency graph contains a cycle involving ${topo.cycleNodes.length} component(s). ` +
|
|
449
|
-
'Re-run `pugi plan --decompose` with a clearer prompt or edit the manifest manually before executing splits in order.');
|
|
450
|
-
lines.push('');
|
|
451
|
-
for (const node of topo.cycleNodes) {
|
|
452
|
-
lines.push(`- ${node}`);
|
|
453
|
-
}
|
|
454
|
-
lines.push('');
|
|
455
|
-
}
|
|
456
|
-
lines.push('## SPLIT_MANIFEST');
|
|
457
|
-
lines.push('');
|
|
458
|
-
lines.push('```json');
|
|
459
|
-
lines.push(JSON.stringify({
|
|
460
|
-
schema: 1,
|
|
461
|
-
sessionId,
|
|
462
|
-
generatedAt,
|
|
463
|
-
components: decomposition.components.map((component, index) => ({
|
|
464
|
-
name: component.name,
|
|
465
|
-
path: `splits/${String(index + 1).padStart(2, '0')}-${component.name}/spec.md`,
|
|
466
|
-
summary: component.summary,
|
|
467
|
-
dependsOn: component.dependsOn,
|
|
468
|
-
})),
|
|
469
|
-
executionOrder: order,
|
|
470
|
-
cycleNodes: topo.cycleNodes,
|
|
471
|
-
}, null, 2));
|
|
472
|
-
lines.push('```');
|
|
473
|
-
lines.push('');
|
|
474
|
-
return lines.join('\n');
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Kahn-style topological sort. The schema already rejected
|
|
478
|
-
* self-references and unknown deps; cycles are still possible (A->B,
|
|
479
|
-
* B->A). On cycle detection we append the remaining nodes verbatim to
|
|
480
|
-
* `order` (so the manifest still renders something useful) and surface
|
|
481
|
-
* the same nodes in `cycleNodes` so the caller can flag the situation
|
|
482
|
-
* loudly.
|
|
483
|
-
*/
|
|
484
|
-
export function topologicalOrder(decomposition) {
|
|
485
|
-
const remaining = new Map();
|
|
486
|
-
for (const component of decomposition.components) {
|
|
487
|
-
remaining.set(component.name, new Set(component.dependsOn));
|
|
488
|
-
}
|
|
489
|
-
const ordered = [];
|
|
490
|
-
const cycleNodes = [];
|
|
491
|
-
while (remaining.size > 0) {
|
|
492
|
-
const ready = [];
|
|
493
|
-
for (const [name, deps] of remaining.entries()) {
|
|
494
|
-
if (deps.size === 0)
|
|
495
|
-
ready.push(name);
|
|
496
|
-
}
|
|
497
|
-
if (ready.length === 0) {
|
|
498
|
-
// Cycle — append the remaining names in declaration order so the
|
|
499
|
-
// manifest is still useful, AND surface the same nodes in the
|
|
500
|
-
// cycle report.
|
|
501
|
-
for (const component of decomposition.components) {
|
|
502
|
-
if (remaining.has(component.name)) {
|
|
503
|
-
ordered.push(component.name);
|
|
504
|
-
cycleNodes.push(component.name);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
// Sort ready bucket alphabetically for determinism: multiple
|
|
510
|
-
// independent roots otherwise come out in `Map.keys()` insertion
|
|
511
|
-
// order, which depends on the upstream JSON which we don't control.
|
|
512
|
-
ready.sort();
|
|
513
|
-
for (const name of ready) {
|
|
514
|
-
ordered.push(name);
|
|
515
|
-
remaining.delete(name);
|
|
516
|
-
for (const deps of remaining.values()) {
|
|
517
|
-
deps.delete(name);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return { order: ordered, cycleNodes };
|
|
522
|
-
}
|
|
523
|
-
function sanitizeMermaidId(name) {
|
|
524
|
-
// Mermaid graph node ids must be valid identifiers (no hyphens at
|
|
525
|
-
// arbitrary positions, no leading digits). We map kebab-case names
|
|
526
|
-
// to safe ids by replacing hyphens with underscores and prefixing
|
|
527
|
-
// a leading non-letter with `n_`.
|
|
528
|
-
const replaced = name.replace(/-/g, '_');
|
|
529
|
-
return /^[A-Za-z]/.test(replaced) ? replaced : `n_${replaced}`;
|
|
530
|
-
}
|
|
531
|
-
//# sourceMappingURL=plan-decompose.js.map
|