okstra 0.174.0 → 0.176.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/README.md +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/{src → dist}/commands/execute/team.mjs +29 -36
- package/dist/commands/execute/team.mjs.map +1 -0
- package/dist/commands/execute/token-usage.d.mts +1 -0
- package/{src → dist}/commands/execute/token-usage.mjs +2 -3
- package/dist/commands/execute/token-usage.mjs.map +1 -0
- package/dist/commands/execute/wizard.d.mts +8 -0
- package/dist/commands/execute/wizard.mjs +191 -0
- package/dist/commands/execute/wizard.mjs.map +1 -0
- package/dist/commands/execute/worker-audit-check.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
- package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
- package/dist/commands/execute/worker-dispatch.d.mts +7 -0
- package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
- package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
- package/dist/commands/execute/worker-state.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-state.mjs +15 -16
- package/dist/commands/execute/worker-state.mjs.map +1 -0
- package/dist/commands/execute/worktree-lookup.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
- package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
- package/dist/commands/execute/worktree-status.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
- package/dist/commands/execute/worktree-status.mjs.map +1 -0
- package/dist/commands/inspect/code-review.d.mts +1 -0
- package/{src → dist}/commands/inspect/code-review.mjs +16 -19
- package/dist/commands/inspect/code-review.mjs.map +1 -0
- package/dist/commands/inspect/container.d.mts +1 -0
- package/{src → dist}/commands/inspect/container.mjs +11 -13
- package/dist/commands/inspect/container.mjs.map +1 -0
- package/dist/commands/inspect/context-cost.d.mts +1 -0
- package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
- package/dist/commands/inspect/context-cost.mjs.map +1 -0
- package/dist/commands/inspect/design-prep.d.mts +1 -0
- package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
- package/dist/commands/inspect/design-prep.mjs.map +1 -0
- package/dist/commands/inspect/error-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-report.mjs +11 -13
- package/dist/commands/inspect/error-report.mjs.map +1 -0
- package/dist/commands/inspect/error-zip.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
- package/dist/commands/inspect/error-zip.mjs.map +1 -0
- package/dist/commands/inspect/log-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/log-report.mjs +11 -13
- package/dist/commands/inspect/log-report.mjs.map +1 -0
- package/dist/commands/inspect/profile-show.d.mts +1 -0
- package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
- package/dist/commands/inspect/profile-show.mjs.map +1 -0
- package/dist/commands/inspect/recap.d.mts +1 -0
- package/{src → dist}/commands/inspect/recap.mjs +11 -13
- package/dist/commands/inspect/recap.mjs.map +1 -0
- package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
- package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
- package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
- package/dist/commands/inspect/rollup.d.mts +1 -0
- package/{src → dist}/commands/inspect/rollup.mjs +11 -13
- package/dist/commands/inspect/rollup.mjs.map +1 -0
- package/dist/commands/inspect/run-audit.d.mts +1 -0
- package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
- package/dist/commands/inspect/run-audit.mjs.map +1 -0
- package/dist/commands/inspect/set-work-status.d.mts +1 -0
- package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
- package/dist/commands/inspect/set-work-status.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.d.mts +1 -0
- package/dist/commands/inspect/stage-map.mjs +110 -0
- package/dist/commands/inspect/stage-map.mjs.map +1 -0
- package/dist/commands/inspect/task-list.d.mts +1 -0
- package/dist/commands/inspect/task-list.mjs +103 -0
- package/dist/commands/inspect/task-list.mjs.map +1 -0
- package/dist/commands/inspect/task-show.d.mts +1 -0
- package/dist/commands/inspect/task-show.mjs +108 -0
- package/dist/commands/inspect/task-show.mjs.map +1 -0
- package/dist/commands/inspect/time-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/time-report.mjs +11 -13
- package/dist/commands/inspect/time-report.mjs.map +1 -0
- package/dist/commands/inspect/usage-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
- package/dist/commands/inspect/usage-report.mjs.map +1 -0
- package/dist/commands/inspect/user-response.d.mts +1 -0
- package/{src → dist}/commands/inspect/user-response.mjs +10 -11
- package/dist/commands/inspect/user-response.mjs.map +1 -0
- package/dist/commands/inspect/worker-liveness.d.mts +1 -0
- package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
- package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
- package/dist/commands/lifecycle/check-project.d.mts +21 -0
- package/dist/commands/lifecycle/check-project.mjs +175 -0
- package/dist/commands/lifecycle/check-project.mjs.map +1 -0
- package/dist/commands/lifecycle/config.d.mts +1 -0
- package/dist/commands/lifecycle/config.mjs +369 -0
- package/dist/commands/lifecycle/config.mjs.map +1 -0
- package/dist/commands/lifecycle/doctor.d.mts +10 -0
- package/dist/commands/lifecycle/doctor.mjs +317 -0
- package/dist/commands/lifecycle/doctor.mjs.map +1 -0
- package/dist/commands/lifecycle/install.d.mts +17 -0
- package/dist/commands/lifecycle/install.mjs +1077 -0
- package/dist/commands/lifecycle/install.mjs.map +1 -0
- package/dist/commands/lifecycle/migrate.d.mts +1 -0
- package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
- package/dist/commands/lifecycle/migrate.mjs.map +1 -0
- package/dist/commands/lifecycle/model.d.mts +1 -0
- package/dist/commands/lifecycle/model.mjs +22 -0
- package/dist/commands/lifecycle/model.mjs.map +1 -0
- package/dist/commands/lifecycle/paths.d.mts +1 -0
- package/dist/commands/lifecycle/paths.mjs +102 -0
- package/dist/commands/lifecycle/paths.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.d.mts +1 -0
- package/dist/commands/lifecycle/preflight.mjs +124 -0
- package/dist/commands/lifecycle/preflight.mjs.map +1 -0
- package/dist/commands/lifecycle/setup.d.mts +1 -0
- package/dist/commands/lifecycle/setup.mjs +297 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -0
- package/dist/commands/lifecycle/uninstall.d.mts +4 -0
- package/dist/commands/lifecycle/uninstall.mjs +262 -0
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
- package/dist/commands/manager.d.mts +3 -0
- package/{src → dist}/commands/manager.mjs +25 -32
- package/dist/commands/manager.mjs.map +1 -0
- package/dist/commands/memory/memory.d.mts +1 -0
- package/dist/commands/memory/memory.mjs +569 -0
- package/dist/commands/memory/memory.mjs.map +1 -0
- package/dist/commands/pr/pr.d.mts +33 -0
- package/dist/commands/pr/pr.mjs +296 -0
- package/dist/commands/pr/pr.mjs.map +1 -0
- package/dist/commands/report/agent-activity.d.mts +1 -0
- package/{src → dist}/commands/report/agent-activity.mjs +11 -12
- package/dist/commands/report/agent-activity.mjs.map +1 -0
- package/dist/commands/report/finalize.d.mts +4 -0
- package/{src → dist}/commands/report/finalize.mjs +25 -32
- package/dist/commands/report/finalize.mjs.map +1 -0
- package/dist/commands/report/inject-report-index.d.mts +1 -0
- package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
- package/dist/commands/report/inject-report-index.mjs.map +1 -0
- package/dist/commands/report/render-final-report.d.mts +1 -0
- package/{src → dist}/commands/report/render-final-report.mjs +6 -7
- package/dist/commands/report/render-final-report.mjs.map +1 -0
- package/dist/commands/report/render-views.d.mts +1 -0
- package/{src → dist}/commands/report/render-views.mjs +6 -7
- package/dist/commands/report/render-views.mjs.map +1 -0
- package/dist/commands/report/translate.d.mts +1 -0
- package/{src → dist}/commands/report/translate.mjs +6 -7
- package/dist/commands/report/translate.mjs.map +1 -0
- package/dist/lib/helper-scripts.d.mts +3 -0
- package/{src → dist}/lib/helper-scripts.mjs +11 -14
- package/dist/lib/helper-scripts.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +25 -0
- package/dist/lib/host-registry-client.mjs +152 -0
- package/dist/lib/host-registry-client.mjs.map +1 -0
- package/dist/lib/install-assets.d.mts +8 -0
- package/{src → dist}/lib/install-assets.mjs +17 -20
- package/dist/lib/install-assets.mjs.map +1 -0
- package/dist/lib/okstra-dirs.d.mts +4 -0
- package/{src → dist}/lib/okstra-dirs.mjs +3 -6
- package/dist/lib/okstra-dirs.mjs.map +1 -0
- package/dist/lib/paths.d.mts +4 -0
- package/dist/lib/paths.mjs +78 -0
- package/dist/lib/paths.mjs.map +1 -0
- package/dist/lib/proc.d.mts +3 -0
- package/dist/lib/proc.mjs +31 -0
- package/dist/lib/proc.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +8 -0
- package/dist/lib/python-helper.mjs +123 -0
- package/dist/lib/python-helper.mjs.map +1 -0
- package/dist/lib/runtime-manifest.d.mts +7 -0
- package/dist/lib/runtime-manifest.mjs +73 -0
- package/dist/lib/runtime-manifest.mjs.map +1 -0
- package/dist/lib/skill-catalog.d.mts +4 -0
- package/dist/lib/skill-catalog.mjs +50 -0
- package/dist/lib/skill-catalog.mjs.map +1 -0
- package/dist/lib/types.d.mts +129 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/lib/version.d.mts +2 -0
- package/dist/lib/version.mjs +27 -0
- package/dist/lib/version.mjs.map +1 -0
- package/docs/architecture/storage-model.md +6 -2
- package/docs/architecture.md +29 -20
- package/docs/cli.md +40 -16
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +72 -62
- package/docs/task-process/README.md +12 -8
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/agents/workers/translator-worker.md +3 -1
- package/runtime/bin/lib/okstra/cli.sh +12 -0
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +7 -6
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +17 -13
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
- package/runtime/prompts/profiles/error-analysis.md +27 -1
- package/runtime/prompts/profiles/feature-analysis.md +20 -0
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
- package/runtime/prompts/profiles/implementation-planning.md +26 -0
- package/runtime/prompts/profiles/implementation.md +19 -0
- package/runtime/prompts/profiles/improvement-discovery.md +21 -1
- package/runtime/prompts/profiles/project-analysis.md +20 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +27 -1
- package/runtime/prompts/wizard/prompts.ko.json +35 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/convergence.py +409 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
- package/runtime/python/okstra_ctl/convergence_store.py +170 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
- package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
- package/runtime/python/okstra_ctl/doctor.py +14 -0
- package/runtime/python/okstra_ctl/domain/host.py +34 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/next_phase.py +236 -0
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +124 -57
- package/runtime/python/okstra_ctl/render_final_report.py +19 -0
- package/runtime/python/okstra_ctl/report_contract.py +39 -0
- package/runtime/python/okstra_ctl/report_finalize.py +16 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +210 -0
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +1210 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1449 -66
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/worker_request.py +7 -1
- package/runtime/python/okstra_ctl/worker_runner.py +130 -5
- package/runtime/python/okstra_ctl/workers.py +7 -5
- package/runtime/python/okstra_ctl/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_project/state.py +54 -6
- package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
- package/runtime/schemas/final-report-v2.0.schema.json +68 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +14 -14
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- package/runtime/templates/report-writer-prompt-preamble.md +2 -0
- package/runtime/templates/reports/final-report-v2.template.md +7 -1
- package/runtime/templates/reports/html/base.template.html +21 -0
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +6 -35
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +486 -134
- package/runtime/validators/validate_session_conformance.py +25 -7
- package/src/cli-registry.mjs +0 -539
- package/src/commands/execute/codex-dispatch.mjs +0 -10
- package/src/commands/execute/plan-items.mjs +0 -9
- package/src/commands/execute/plan-validate.mjs +0 -68
- package/src/commands/execute/run.mjs +0 -208
- package/src/commands/execute/wizard.mjs +0 -167
- package/src/commands/inspect/stage-map.mjs +0 -98
- package/src/commands/inspect/task-list.mjs +0 -97
- package/src/commands/inspect/task-show.mjs +0 -100
- package/src/commands/lifecycle/check-project.mjs +0 -187
- package/src/commands/lifecycle/config.mjs +0 -385
- package/src/commands/lifecycle/doctor.mjs +0 -308
- package/src/commands/lifecycle/install.mjs +0 -1170
- package/src/commands/lifecycle/paths.mjs +0 -104
- package/src/commands/lifecycle/preflight.mjs +0 -125
- package/src/commands/lifecycle/setup.mjs +0 -333
- package/src/commands/lifecycle/uninstall.mjs +0 -279
- package/src/commands/memory/memory.mjs +0 -524
- package/src/commands/pr/pr.mjs +0 -261
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
-
import { promises as fs } from "node:fs";
|
|
5
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
6
|
-
import { resolveOkstraHome } from "../../lib/paths.mjs";
|
|
7
|
-
|
|
8
|
-
const MEMORY_TYPES = new Set([
|
|
9
|
-
"context",
|
|
10
|
-
"decision",
|
|
11
|
-
"preference",
|
|
12
|
-
"requirement",
|
|
13
|
-
"person",
|
|
14
|
-
"project-hint",
|
|
15
|
-
"follow-up",
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
// High-confidence secret shapes. The skill instructs the agent to omit secrets,
|
|
19
|
-
// but this is the CLI-side enforcement (declaration → enforcement) so a stray
|
|
20
|
-
// credential cannot be persisted. Patterns are intentionally narrow (known token
|
|
21
|
-
// prefixes / key blocks) to avoid false-positives on prose that merely mentions
|
|
22
|
-
// "password" or "token".
|
|
23
|
-
const SECRET_PATTERNS = [
|
|
24
|
-
[/-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, "private key block"],
|
|
25
|
-
[/\bAKIA[0-9A-Z]{16}\b/, "AWS access key id"],
|
|
26
|
-
[/\bASIA[0-9A-Z]{16}\b/, "AWS temporary access key id"],
|
|
27
|
-
[/\bgh[posru]_[A-Za-z0-9]{36,}\b/, "GitHub token"],
|
|
28
|
-
[/\bgithub_pat_[A-Za-z0-9_]{60,}\b/, "GitHub fine-grained token"],
|
|
29
|
-
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, "Slack token"],
|
|
30
|
-
[/\bAIza[0-9A-Za-z_-]{35}\b/, "Google API key"],
|
|
31
|
-
[/\bsk-[A-Za-z0-9]{20,}\b/, "OpenAI-style secret key"],
|
|
32
|
-
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/, "JWT"],
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
function detectSecrets(content) {
|
|
36
|
-
const hits = [];
|
|
37
|
-
for (const [pattern, label] of SECRET_PATTERNS) {
|
|
38
|
-
if (pattern.test(content)) hits.push(label);
|
|
39
|
-
}
|
|
40
|
-
return [...new Set(hits)];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const USAGE = `okstra memory — store and find conversation memory
|
|
44
|
-
|
|
45
|
-
Memory Book stores global, user-home notes under ~/.okstra/memory-book.
|
|
46
|
-
It is separate from project-local .okstra task artifacts.
|
|
47
|
-
|
|
48
|
-
Usage:
|
|
49
|
-
okstra memory add [--content <text> | --file <path>] [options]
|
|
50
|
-
okstra memory list [--limit <n>] [--tag <tag>] [--type <type>]
|
|
51
|
-
[--project-group <name>] [--json]
|
|
52
|
-
okstra memory search <query> [--limit <n>] [--project-group <name>]
|
|
53
|
-
[--include-archived] [--json]
|
|
54
|
-
okstra memory groups [--include-archived] [--json]
|
|
55
|
-
okstra memory show <id> [--json]
|
|
56
|
-
okstra memory archive <id> [--json]
|
|
57
|
-
|
|
58
|
-
Add options:
|
|
59
|
-
--title <title> Entry title. Defaults to the first non-empty line.
|
|
60
|
-
--type <type> context|decision|preference|requirement|person|
|
|
61
|
-
project-hint|follow-up. Default: context.
|
|
62
|
-
--project-group <name> Project group label (e.g. acme, private). Default: global.
|
|
63
|
-
--project <id> Related project id. Repeatable.
|
|
64
|
-
--tag <tag> Tag. Repeatable or comma-separated.
|
|
65
|
-
--source <source> Source label. Default: conversation.
|
|
66
|
-
--yes Skip interactive confirmation.
|
|
67
|
-
--json Emit JSON.
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
|
-
function okstraHome() {
|
|
71
|
-
return resolveOkstraHome();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function memoryRoot() {
|
|
75
|
-
return join(okstraHome(), "memory-book");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function indexPath() {
|
|
79
|
-
return join(memoryRoot(), "index.jsonl");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function parseGlobalFlags(args) {
|
|
83
|
-
return {
|
|
84
|
-
json: args.includes("--json"),
|
|
85
|
-
includeArchived: args.includes("--include-archived"),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function takeValue(args, index, flag) {
|
|
90
|
-
const value = args[index + 1];
|
|
91
|
-
if (!value || value.startsWith("--")) {
|
|
92
|
-
throw new Error(`${flag} requires a value`);
|
|
93
|
-
}
|
|
94
|
-
return value;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function parseAddArgs(args) {
|
|
98
|
-
const opts = {
|
|
99
|
-
content: null,
|
|
100
|
-
file: null,
|
|
101
|
-
title: null,
|
|
102
|
-
type: "context",
|
|
103
|
-
projectGroup: "global",
|
|
104
|
-
source: "conversation",
|
|
105
|
-
tags: [],
|
|
106
|
-
projects: [],
|
|
107
|
-
yes: false,
|
|
108
|
-
json: false,
|
|
109
|
-
};
|
|
110
|
-
for (let i = 0; i < args.length; i++) {
|
|
111
|
-
const flag = args[i];
|
|
112
|
-
if (flag === "--content") opts.content = takeValue(args, i++, flag);
|
|
113
|
-
else if (flag === "--file") opts.file = takeValue(args, i++, flag);
|
|
114
|
-
else if (flag === "--title") opts.title = takeValue(args, i++, flag);
|
|
115
|
-
else if (flag === "--type") opts.type = takeValue(args, i++, flag);
|
|
116
|
-
else if (flag === "--project-group") opts.projectGroup = takeValue(args, i++, flag);
|
|
117
|
-
else if (flag === "--source") opts.source = takeValue(args, i++, flag);
|
|
118
|
-
else if (flag === "--project") opts.projects.push(takeValue(args, i++, flag));
|
|
119
|
-
else if (flag === "--tag") opts.tags.push(...splitCsv(takeValue(args, i++, flag)));
|
|
120
|
-
else if (flag === "--yes") opts.yes = true;
|
|
121
|
-
else if (flag === "--json") opts.json = true;
|
|
122
|
-
else throw new Error(`unknown flag ${flag}`);
|
|
123
|
-
}
|
|
124
|
-
if (!MEMORY_TYPES.has(opts.type)) {
|
|
125
|
-
throw new Error(`invalid --type: ${opts.type}`);
|
|
126
|
-
}
|
|
127
|
-
if (opts.content !== null && opts.file !== null) {
|
|
128
|
-
throw new Error("pass only one of --content or --file");
|
|
129
|
-
}
|
|
130
|
-
return opts;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function parseListArgs(args) {
|
|
134
|
-
const opts = { ...parseGlobalFlags(args), limit: 20, tag: null, type: null, projectGroup: null };
|
|
135
|
-
for (let i = 0; i < args.length; i++) {
|
|
136
|
-
const flag = args[i];
|
|
137
|
-
if (flag === "--json" || flag === "--include-archived") continue;
|
|
138
|
-
if (flag === "--limit") opts.limit = parseLimit(takeValue(args, i++, flag));
|
|
139
|
-
else if (flag === "--tag") opts.tag = takeValue(args, i++, flag);
|
|
140
|
-
else if (flag === "--type") opts.type = takeValue(args, i++, flag);
|
|
141
|
-
else if (flag === "--project-group") opts.projectGroup = takeValue(args, i++, flag);
|
|
142
|
-
else throw new Error(`unknown flag ${flag}`);
|
|
143
|
-
}
|
|
144
|
-
return opts;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function parseQueryArgs(args) {
|
|
148
|
-
const opts = { ...parseGlobalFlags(args), limit: 20, query: [], projectGroup: null };
|
|
149
|
-
for (let i = 0; i < args.length; i++) {
|
|
150
|
-
const flag = args[i];
|
|
151
|
-
if (flag === "--json" || flag === "--include-archived") continue;
|
|
152
|
-
if (flag === "--limit") opts.limit = parseLimit(takeValue(args, i++, flag));
|
|
153
|
-
else if (flag === "--project-group") opts.projectGroup = takeValue(args, i++, flag);
|
|
154
|
-
else if (flag.startsWith("--")) throw new Error(`unknown flag ${flag}`);
|
|
155
|
-
else opts.query.push(flag);
|
|
156
|
-
}
|
|
157
|
-
if (opts.query.length === 0) throw new Error("search requires a query");
|
|
158
|
-
return { ...opts, query: opts.query.join(" ").trim() };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function parseGroupsArgs(args) {
|
|
162
|
-
for (const flag of args) {
|
|
163
|
-
if (flag !== "--json" && flag !== "--include-archived") {
|
|
164
|
-
throw new Error(`unknown flag ${flag}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return parseGlobalFlags(args);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function entryGroup(entry) {
|
|
171
|
-
return entry.projectGroup ?? "global";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function parseLimit(raw) {
|
|
175
|
-
const value = Number.parseInt(raw, 10);
|
|
176
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
177
|
-
throw new Error("--limit must be a positive integer");
|
|
178
|
-
}
|
|
179
|
-
return value;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function splitCsv(value) {
|
|
183
|
-
return value
|
|
184
|
-
.split(",")
|
|
185
|
-
.map((part) => part.trim())
|
|
186
|
-
.filter(Boolean);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function readInputContent(opts) {
|
|
190
|
-
if (opts.content !== null) return opts.content;
|
|
191
|
-
if (opts.file !== null) return fs.readFile(resolve(opts.file), "utf8");
|
|
192
|
-
if (!process.stdin.isTTY) return readStdin();
|
|
193
|
-
throw new Error("memory add requires --content, --file, or stdin");
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function readStdin() {
|
|
197
|
-
const chunks = [];
|
|
198
|
-
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
199
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function inferTitle(content, explicitTitle, filePath) {
|
|
203
|
-
if (explicitTitle?.trim()) return explicitTitle.trim();
|
|
204
|
-
const firstLine = content
|
|
205
|
-
.split(/\r?\n/)
|
|
206
|
-
.map((line) => line.trim())
|
|
207
|
-
.find(Boolean);
|
|
208
|
-
if (firstLine) return truncate(firstLine.replace(/^#+\s*/, ""), 80);
|
|
209
|
-
if (filePath) return basename(filePath);
|
|
210
|
-
return "Untitled memory";
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function truncate(value, maxLength) {
|
|
214
|
-
return value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function slugify(value) {
|
|
218
|
-
const slug = value
|
|
219
|
-
.toLowerCase()
|
|
220
|
-
.replace(/[^a-z0-9가-힣]+/g, "-")
|
|
221
|
-
.replace(/^-+|-+$/g, "")
|
|
222
|
-
.slice(0, 48);
|
|
223
|
-
return slug || "memory";
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function buildEntry(opts, content, now) {
|
|
227
|
-
const title = inferTitle(content, opts.title, opts.file);
|
|
228
|
-
const compact = now.toISOString().replace(/\D/g, "").slice(0, 17);
|
|
229
|
-
const slug = slugify(title);
|
|
230
|
-
// Random suffix disambiguates two same-title adds within one millisecond,
|
|
231
|
-
// which would otherwise collide on both id and on-disk path (silent overwrite).
|
|
232
|
-
const nonce = randomBytes(3).toString("hex");
|
|
233
|
-
const id = `mem_${compact}_${slug}_${nonce}`;
|
|
234
|
-
const relativePath = join(
|
|
235
|
-
"entries",
|
|
236
|
-
String(now.getUTCFullYear()),
|
|
237
|
-
String(now.getUTCMonth() + 1).padStart(2, "0"),
|
|
238
|
-
`${now.toISOString().replace(/[:.]/g, "")}-${slug}-${nonce}.md`,
|
|
239
|
-
);
|
|
240
|
-
return {
|
|
241
|
-
id,
|
|
242
|
-
title,
|
|
243
|
-
type: opts.type,
|
|
244
|
-
projectGroup: opts.projectGroup,
|
|
245
|
-
source: opts.source,
|
|
246
|
-
tags: [...new Set(opts.tags)],
|
|
247
|
-
relatedProjects: [...new Set(opts.projects)],
|
|
248
|
-
status: "active",
|
|
249
|
-
createdAt: now.toISOString(),
|
|
250
|
-
archivedAt: null,
|
|
251
|
-
path: relativePath,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function renderMarkdown(entry, content) {
|
|
256
|
-
return [
|
|
257
|
-
"---",
|
|
258
|
-
`id: ${entry.id}`,
|
|
259
|
-
`createdAt: ${entry.createdAt}`,
|
|
260
|
-
`source: ${entry.source}`,
|
|
261
|
-
`project-group: ${entry.projectGroup}`,
|
|
262
|
-
`type: ${entry.type}`,
|
|
263
|
-
`status: ${entry.status}`,
|
|
264
|
-
`relatedProjects: [${entry.relatedProjects.join(", ")}]`,
|
|
265
|
-
`tags: [${entry.tags.join(", ")}]`,
|
|
266
|
-
"---",
|
|
267
|
-
"",
|
|
268
|
-
`# ${entry.title}`,
|
|
269
|
-
"",
|
|
270
|
-
"## Summary",
|
|
271
|
-
"",
|
|
272
|
-
content.trim() || "(empty)",
|
|
273
|
-
"",
|
|
274
|
-
].join("\n");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async function confirmSave(entry, content, opts) {
|
|
278
|
-
if (opts.yes || opts.json || !process.stdin.isTTY) return true;
|
|
279
|
-
process.stdout.write(`Memory Book entry:\n`);
|
|
280
|
-
process.stdout.write(` title: ${entry.title}\n`);
|
|
281
|
-
process.stdout.write(` type: ${entry.type}\n`);
|
|
282
|
-
process.stdout.write(` group: ${entry.projectGroup}\n`);
|
|
283
|
-
process.stdout.write(` tags: ${entry.tags.join(", ") || "(none)"}\n`);
|
|
284
|
-
process.stdout.write(` text: ${truncate(content.trim().replace(/\s+/g, " "), 140)}\n`);
|
|
285
|
-
const rl = createInterface({ input, output });
|
|
286
|
-
const answer = await rl.question("Save to ~/.okstra/memory-book? [y/N] ");
|
|
287
|
-
rl.close();
|
|
288
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async function appendIndex(entry) {
|
|
292
|
-
await fs.mkdir(dirname(indexPath()), { recursive: true });
|
|
293
|
-
await fs.appendFile(indexPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function readIndex() {
|
|
297
|
-
let text;
|
|
298
|
-
try {
|
|
299
|
-
text = await fs.readFile(indexPath(), "utf8");
|
|
300
|
-
} catch (err) {
|
|
301
|
-
if (err.code === "ENOENT") return [];
|
|
302
|
-
throw err;
|
|
303
|
-
}
|
|
304
|
-
// Skip lines that fail to parse so one truncated row (e.g. an interrupted
|
|
305
|
-
// appendIndex) cannot make the whole Memory Book unreadable; other JSONL
|
|
306
|
-
// readers in okstra tolerate corruption the same way.
|
|
307
|
-
const entries = [];
|
|
308
|
-
for (const line of text.split(/\r?\n/)) {
|
|
309
|
-
if (!line) continue;
|
|
310
|
-
try {
|
|
311
|
-
entries.push(JSON.parse(line));
|
|
312
|
-
} catch {
|
|
313
|
-
// Drop the unparseable row; intact entries remain visible.
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return entries;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async function writeIndex(entries) {
|
|
320
|
-
await fs.mkdir(dirname(indexPath()), { recursive: true });
|
|
321
|
-
const data = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
322
|
-
await fs.writeFile(indexPath(), data ? `${data}\n` : "", "utf8");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function visibleEntries(entries, opts) {
|
|
326
|
-
return entries.filter((entry) => opts.includeArchived || entry.status !== "archived");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function formatEntryLine(entry) {
|
|
330
|
-
const tags = entry.tags.length > 0 ? ` #${entry.tags.join(" #")}` : "";
|
|
331
|
-
return `${entry.id} ${entry.createdAt.slice(0, 10)} ${entry.type} ${entry.title}${tags}`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function emitJson(payload) {
|
|
335
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
async function opAdd(args) {
|
|
339
|
-
const opts = parseAddArgs(args);
|
|
340
|
-
const content = await readInputContent(opts);
|
|
341
|
-
const secrets = detectSecrets(content);
|
|
342
|
-
if (secrets.length > 0) {
|
|
343
|
-
throw new Error(
|
|
344
|
-
`refusing to store likely secret(s): ${secrets.join(", ")}. ` +
|
|
345
|
-
"Memory Book must not hold credentials — redact the secret and store a note instead.",
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
const entry = buildEntry(opts, content, new Date());
|
|
349
|
-
if (!(await confirmSave(entry, content, opts))) {
|
|
350
|
-
process.stdout.write("cancelled\n");
|
|
351
|
-
return 0;
|
|
352
|
-
}
|
|
353
|
-
const absolutePath = join(memoryRoot(), entry.path);
|
|
354
|
-
await fs.mkdir(dirname(absolutePath), { recursive: true });
|
|
355
|
-
await fs.writeFile(absolutePath, renderMarkdown(entry, content), "utf8");
|
|
356
|
-
try {
|
|
357
|
-
await appendIndex(entry);
|
|
358
|
-
} catch (err) {
|
|
359
|
-
// Index append failed after the file was written — remove the orphan so
|
|
360
|
-
// `add` stays all-or-nothing (an entry with no index row is invisible to
|
|
361
|
-
// list/search anyway).
|
|
362
|
-
await fs.rm(absolutePath, { force: true });
|
|
363
|
-
throw err;
|
|
364
|
-
}
|
|
365
|
-
if (opts.json) emitJson({ ok: true, entry, path: absolutePath });
|
|
366
|
-
else process.stdout.write(`saved ${entry.id}\n${absolutePath}\n`);
|
|
367
|
-
return 0;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async function opList(args) {
|
|
371
|
-
const opts = parseListArgs(args);
|
|
372
|
-
const entries = visibleEntries(await readIndex(), opts)
|
|
373
|
-
.filter((entry) => !opts.tag || entry.tags.includes(opts.tag))
|
|
374
|
-
.filter((entry) => !opts.type || entry.type === opts.type)
|
|
375
|
-
.filter((entry) => !opts.projectGroup || entryGroup(entry) === opts.projectGroup)
|
|
376
|
-
.slice(0, opts.limit);
|
|
377
|
-
if (opts.json) emitJson(entries);
|
|
378
|
-
else process.stdout.write(entries.map(formatEntryLine).join("\n") + (entries.length ? "\n" : ""));
|
|
379
|
-
return 0;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function opSearch(args) {
|
|
383
|
-
const opts = parseQueryArgs(args);
|
|
384
|
-
const needle = opts.query.toLowerCase();
|
|
385
|
-
const entries = visibleEntries(await readIndex(), opts).filter(
|
|
386
|
-
(entry) => !opts.projectGroup || entryGroup(entry) === opts.projectGroup,
|
|
387
|
-
);
|
|
388
|
-
const matches = [];
|
|
389
|
-
for (const entry of entries) {
|
|
390
|
-
if (await entryMatches(entry, needle)) matches.push(entry);
|
|
391
|
-
if (matches.length >= opts.limit) break;
|
|
392
|
-
}
|
|
393
|
-
if (opts.json) emitJson(matches);
|
|
394
|
-
else process.stdout.write(matches.map(formatEntryLine).join("\n") + (matches.length ? "\n" : ""));
|
|
395
|
-
return 0;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
async function opGroups(args) {
|
|
399
|
-
const opts = parseGroupsArgs(args);
|
|
400
|
-
const counts = new Map();
|
|
401
|
-
for (const entry of visibleEntries(await readIndex(), opts)) {
|
|
402
|
-
const group = entryGroup(entry);
|
|
403
|
-
counts.set(group, (counts.get(group) ?? 0) + 1);
|
|
404
|
-
}
|
|
405
|
-
const groups = [...counts.entries()]
|
|
406
|
-
.map(([name, count]) => ({ name, count }))
|
|
407
|
-
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
408
|
-
if (opts.json) emitJson(groups);
|
|
409
|
-
else process.stdout.write(groups.map((g) => `${g.name} (${g.count})`).join("\n") + (groups.length ? "\n" : ""));
|
|
410
|
-
return 0;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
async function entryMatches(entry, needle) {
|
|
414
|
-
const haystack = [
|
|
415
|
-
entry.id,
|
|
416
|
-
entry.title,
|
|
417
|
-
entry.type,
|
|
418
|
-
entryGroup(entry),
|
|
419
|
-
entry.source,
|
|
420
|
-
...entry.tags,
|
|
421
|
-
...entry.relatedProjects,
|
|
422
|
-
]
|
|
423
|
-
.join(" ")
|
|
424
|
-
.toLowerCase();
|
|
425
|
-
if (haystack.includes(needle)) return true;
|
|
426
|
-
try {
|
|
427
|
-
const body = await fs.readFile(join(memoryRoot(), entry.path), "utf8");
|
|
428
|
-
return body.toLowerCase().includes(needle);
|
|
429
|
-
} catch {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async function findEntry(id) {
|
|
435
|
-
const entries = await readIndex();
|
|
436
|
-
const entry = entries.find((candidate) => candidate.id === id);
|
|
437
|
-
if (!entry) throw new Error(`memory entry not found: ${id}`);
|
|
438
|
-
return { entry, entries };
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async function opShow(args) {
|
|
442
|
-
const json = args.includes("--json");
|
|
443
|
-
const ids = args.filter((arg) => arg !== "--json");
|
|
444
|
-
if (ids.length !== 1) throw new Error("show requires exactly one id");
|
|
445
|
-
const { entry } = await findEntry(ids[0]);
|
|
446
|
-
const absolutePath = join(memoryRoot(), entry.path);
|
|
447
|
-
const body = await fs.readFile(absolutePath, "utf8");
|
|
448
|
-
if (json) emitJson({ entry, path: absolutePath, body });
|
|
449
|
-
else process.stdout.write(body);
|
|
450
|
-
return 0;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
async function opArchive(args) {
|
|
454
|
-
const json = args.includes("--json");
|
|
455
|
-
const ids = args.filter((arg) => arg !== "--json");
|
|
456
|
-
if (ids.length !== 1) throw new Error("archive requires exactly one id");
|
|
457
|
-
const { entry, entries } = await findEntry(ids[0]);
|
|
458
|
-
const archivedEntry = await moveToArchive(entry);
|
|
459
|
-
const updated = entries.map((candidate) =>
|
|
460
|
-
candidate.id === entry.id ? archivedEntry : candidate,
|
|
461
|
-
);
|
|
462
|
-
try {
|
|
463
|
-
await writeIndex(updated);
|
|
464
|
-
} catch (err) {
|
|
465
|
-
// Index rewrite failed after the file was already moved — move it back so
|
|
466
|
-
// the index keeps pointing at the file's real location (mirrors opAdd).
|
|
467
|
-
if (archivedEntry.path !== entry.path) {
|
|
468
|
-
await fs.rename(
|
|
469
|
-
join(memoryRoot(), archivedEntry.path),
|
|
470
|
-
join(memoryRoot(), entry.path),
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
throw err;
|
|
474
|
-
}
|
|
475
|
-
if (json) emitJson({ ok: true, entry: archivedEntry });
|
|
476
|
-
else process.stdout.write(`archived ${entry.id}\n`);
|
|
477
|
-
return 0;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async function moveToArchive(entry) {
|
|
481
|
-
if (entry.status === "archived") return entry;
|
|
482
|
-
const sourcePath = join(memoryRoot(), entry.path);
|
|
483
|
-
const archiveDir = join("archive", entry.createdAt.slice(0, 4));
|
|
484
|
-
const nextPath = join(archiveDir, basename(entry.path));
|
|
485
|
-
const targetPath = join(memoryRoot(), nextPath);
|
|
486
|
-
await fs.mkdir(dirname(targetPath), { recursive: true });
|
|
487
|
-
await fs.rename(sourcePath, targetPath);
|
|
488
|
-
return {
|
|
489
|
-
...entry,
|
|
490
|
-
status: "archived",
|
|
491
|
-
archivedAt: new Date().toISOString(),
|
|
492
|
-
path: relative(memoryRoot(), targetPath),
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export async function run(args) {
|
|
497
|
-
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
498
|
-
process.stdout.write(USAGE);
|
|
499
|
-
return args.length === 0 ? 2 : 0;
|
|
500
|
-
}
|
|
501
|
-
const [op, ...rest] = args;
|
|
502
|
-
try {
|
|
503
|
-
switch (op) {
|
|
504
|
-
case "add":
|
|
505
|
-
return await opAdd(rest);
|
|
506
|
-
case "list":
|
|
507
|
-
return await opList(rest);
|
|
508
|
-
case "search":
|
|
509
|
-
return await opSearch(rest);
|
|
510
|
-
case "groups":
|
|
511
|
-
return await opGroups(rest);
|
|
512
|
-
case "show":
|
|
513
|
-
return await opShow(rest);
|
|
514
|
-
case "archive":
|
|
515
|
-
return await opArchive(rest);
|
|
516
|
-
default:
|
|
517
|
-
process.stderr.write(`unknown memory subcommand: ${op}\n\n${USAGE}`);
|
|
518
|
-
return 2;
|
|
519
|
-
}
|
|
520
|
-
} catch (err) {
|
|
521
|
-
process.stderr.write(`error: ${err.message}\n`);
|
|
522
|
-
return 1;
|
|
523
|
-
}
|
|
524
|
-
}
|