devcouncil 0.3.1 → 0.4.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 +46 -30
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +49 -30
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +960 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +303 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- package/src/devcouncil/integrations/graphify.py +0 -34
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from devcouncil.utils.json_persist import dump_json
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
6
|
import time
|
|
@@ -30,6 +30,7 @@ from devcouncil.app.config import get_api_key, load_config
|
|
|
30
30
|
from devcouncil.llm.provider import create_provider, validate_model_provider
|
|
31
31
|
from devcouncil.llm.router import ModelRouter
|
|
32
32
|
from devcouncil.telemetry.traces import TraceLogger
|
|
33
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
33
34
|
|
|
34
35
|
app = typer.Typer(help="Review active coding-agent sessions and emit critique cards.")
|
|
35
36
|
console = Console()
|
|
@@ -44,19 +45,26 @@ def sessions(
|
|
|
44
45
|
):
|
|
45
46
|
"""List coding-agent transcripts DevCouncil can review."""
|
|
46
47
|
root = project_root.expanduser().resolve()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
49
|
+
set_log_dir(root)
|
|
50
|
+
logger.info("dev watch sessions: client=%s", client)
|
|
51
|
+
with log_stage("watch", project_root=root, subcommand="sessions", client=client):
|
|
52
|
+
log_step("watch/1: discovering sessions", project_root=root, trace=True)
|
|
53
|
+
found = discover_sessions(root, client=client)
|
|
54
|
+
if json_format:
|
|
55
|
+
typer.echo(dump_json({"sessions": [item.model_dump() for item in found]}, indent=2))
|
|
56
|
+
log_step("watch/complete", project_root=root, count=len(found), trace=True)
|
|
57
|
+
return
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
table = Table(title="DevCouncil Watch Sessions")
|
|
60
|
+
table.add_column("Client", style="cyan")
|
|
61
|
+
table.add_column("Session")
|
|
62
|
+
table.add_column("Turns", justify="right")
|
|
63
|
+
table.add_column("Transcript")
|
|
64
|
+
for item in found:
|
|
65
|
+
table.add_row(item.client, item.id, str(item.turns), item.transcript_path)
|
|
66
|
+
console.print(table)
|
|
67
|
+
log_step("watch/complete", project_root=root, count=len(found), trace=True)
|
|
60
68
|
|
|
61
69
|
|
|
62
70
|
@app.command("review")
|
|
@@ -81,7 +89,7 @@ def review(
|
|
|
81
89
|
if transcript_path is None:
|
|
82
90
|
message = "No transcript selected. Use --transcript, --session, or --latest."
|
|
83
91
|
if json_format:
|
|
84
|
-
typer.echo(
|
|
92
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
85
93
|
else:
|
|
86
94
|
console.print(f"[red]{message}[/red]")
|
|
87
95
|
raise typer.Exit(code=2)
|
|
@@ -89,7 +97,7 @@ def review(
|
|
|
89
97
|
if turn is None:
|
|
90
98
|
message = f"No assistant turn found in {transcript_path}."
|
|
91
99
|
if json_format:
|
|
92
|
-
typer.echo(
|
|
100
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
93
101
|
else:
|
|
94
102
|
console.print(f"[yellow]{message}[/yellow]")
|
|
95
103
|
raise typer.Exit(code=1)
|
|
@@ -106,7 +114,7 @@ def review(
|
|
|
106
114
|
payload["duplicate"] = duplicate
|
|
107
115
|
|
|
108
116
|
if json_format:
|
|
109
|
-
typer.echo(
|
|
117
|
+
typer.echo(dump_json(payload, indent=2))
|
|
110
118
|
return
|
|
111
119
|
|
|
112
120
|
_print_card(card)
|
|
@@ -129,7 +137,7 @@ def cards(
|
|
|
129
137
|
if limit < 1:
|
|
130
138
|
message = "--limit must be greater than 0."
|
|
131
139
|
if json_format:
|
|
132
|
-
typer.echo(
|
|
140
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
133
141
|
else:
|
|
134
142
|
console.print(f"[red]{message}[/red]")
|
|
135
143
|
raise typer.Exit(code=2)
|
|
@@ -142,14 +150,14 @@ def cards(
|
|
|
142
150
|
)
|
|
143
151
|
if error:
|
|
144
152
|
if json_format:
|
|
145
|
-
typer.echo(
|
|
153
|
+
typer.echo(dump_json({"ok": False, "error": error}, indent=2))
|
|
146
154
|
else:
|
|
147
155
|
console.print(f"[red]{error}[/red]")
|
|
148
156
|
raise typer.Exit(code=2)
|
|
149
157
|
total = len(found)
|
|
150
158
|
found = found[:limit]
|
|
151
159
|
if json_format:
|
|
152
|
-
typer.echo(
|
|
160
|
+
typer.echo(dump_json({
|
|
153
161
|
"cards": [item.model_dump() for item in found],
|
|
154
162
|
"filters": {
|
|
155
163
|
"task_id": task_id,
|
|
@@ -175,7 +183,7 @@ def status(
|
|
|
175
183
|
root = project_root.expanduser().resolve()
|
|
176
184
|
payload = live_review_summary(root, task_id=task_id)
|
|
177
185
|
if json_format:
|
|
178
|
-
typer.echo(
|
|
186
|
+
typer.echo(dump_json(payload, indent=2))
|
|
179
187
|
return
|
|
180
188
|
blockers = payload["blocking_cards"]
|
|
181
189
|
|
|
@@ -232,7 +240,7 @@ def resolve(
|
|
|
232
240
|
if status not in {"resolved", "ignored"}:
|
|
233
241
|
message = "--status must be resolved or ignored."
|
|
234
242
|
if json_format:
|
|
235
|
-
typer.echo(
|
|
243
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
236
244
|
else:
|
|
237
245
|
console.print(f"[red]{message}[/red]")
|
|
238
246
|
raise typer.Exit(code=2)
|
|
@@ -241,12 +249,12 @@ def resolve(
|
|
|
241
249
|
if card is None:
|
|
242
250
|
message = f"Critique card {card_id} not found."
|
|
243
251
|
if json_format:
|
|
244
|
-
typer.echo(
|
|
252
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
245
253
|
else:
|
|
246
254
|
console.print(f"[red]{message}[/red]")
|
|
247
255
|
raise typer.Exit(code=1)
|
|
248
256
|
if json_format:
|
|
249
|
-
typer.echo(
|
|
257
|
+
typer.echo(dump_json({"ok": True, "card": card.model_dump()}, indent=2))
|
|
250
258
|
_log_card_resolved(root, card)
|
|
251
259
|
return
|
|
252
260
|
_log_card_resolved(root, card)
|
|
@@ -265,13 +273,13 @@ def repair(
|
|
|
265
273
|
if card is None:
|
|
266
274
|
message = f"Critique card {card_id} not found."
|
|
267
275
|
if json_format:
|
|
268
|
-
typer.echo(
|
|
276
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
269
277
|
else:
|
|
270
278
|
console.print(f"[red]{message}[/red]")
|
|
271
279
|
raise typer.Exit(code=1)
|
|
272
280
|
prompt = build_live_repair_prompt(root, card)
|
|
273
281
|
if json_format:
|
|
274
|
-
typer.echo(
|
|
282
|
+
typer.echo(dump_json({"ok": True, "card": card.model_dump(), "prompt": prompt}, indent=2))
|
|
275
283
|
return
|
|
276
284
|
typer.echo(prompt)
|
|
277
285
|
|
|
@@ -296,7 +304,7 @@ def repair_all(
|
|
|
296
304
|
resolved_cards = [card for card in cards if card is not None]
|
|
297
305
|
prompt = build_bulk_live_repair_prompt(root, resolved_cards)
|
|
298
306
|
if json_format:
|
|
299
|
-
typer.echo(
|
|
307
|
+
typer.echo(dump_json({
|
|
300
308
|
"ok": True,
|
|
301
309
|
"scope_task_id": summary["scope_task_id"],
|
|
302
310
|
"cards": [card.model_dump() for card in resolved_cards],
|
|
@@ -316,7 +324,7 @@ def signals(
|
|
|
316
324
|
root = project_root.expanduser().resolve()
|
|
317
325
|
found = _filtered_signals(root, client)
|
|
318
326
|
if json_format:
|
|
319
|
-
typer.echo(
|
|
327
|
+
typer.echo(dump_json({"signals": [item.model_dump() for item in found]}, indent=2))
|
|
320
328
|
return
|
|
321
329
|
table = Table(title="DevCouncil Watch Signals")
|
|
322
330
|
table.add_column("Client", style="cyan")
|
|
@@ -378,7 +386,7 @@ def pending(
|
|
|
378
386
|
|
|
379
387
|
logger.info("dev watch pending complete: %d reviewed, %d skipped", len(reviewed), len(skipped))
|
|
380
388
|
if json_format:
|
|
381
|
-
typer.echo(
|
|
389
|
+
typer.echo(dump_json({"reviewed": reviewed, "skipped": skipped}, indent=2))
|
|
382
390
|
return
|
|
383
391
|
for item in skipped:
|
|
384
392
|
console.print(f"[yellow]Skipped signal:[/yellow] {item['reason']}")
|
|
@@ -441,7 +449,7 @@ def import_transcript(
|
|
|
441
449
|
"""Normalize a transcript into DevCouncil turn records without reviewing it."""
|
|
442
450
|
turns = load_turns(transcript.expanduser().resolve(), client=client)
|
|
443
451
|
if json_format:
|
|
444
|
-
typer.echo(
|
|
452
|
+
typer.echo(dump_json({"turns": [turn.model_dump() for turn in turns]}, indent=2))
|
|
445
453
|
return
|
|
446
454
|
console.print(f"Loaded {len(turns)} turns from {transcript}")
|
|
447
455
|
|
|
@@ -492,6 +500,7 @@ def _resolve_transcript(
|
|
|
492
500
|
transcript: Path | None = None,
|
|
493
501
|
session: str | None = None,
|
|
494
502
|
latest: bool = False,
|
|
503
|
+
task_id: str | None = None,
|
|
495
504
|
) -> Path | None:
|
|
496
505
|
if transcript is not None:
|
|
497
506
|
path = transcript.expanduser()
|
|
@@ -499,6 +508,13 @@ def _resolve_transcript(
|
|
|
499
508
|
path = root / path
|
|
500
509
|
return path.resolve()
|
|
501
510
|
|
|
511
|
+
if client.lower() == "claude" and task_id:
|
|
512
|
+
from devcouncil.live.transcripts import claude_transcript_for_task
|
|
513
|
+
|
|
514
|
+
pinned = claude_transcript_for_task(root, task_id)
|
|
515
|
+
if pinned is not None:
|
|
516
|
+
return pinned
|
|
517
|
+
|
|
502
518
|
selector = "latest" if latest else session
|
|
503
519
|
if selector is None:
|
|
504
520
|
return None
|
|
@@ -590,5 +606,8 @@ async def _review_turn(turn, root: Path, client: str, use_llm: bool, task_id: st
|
|
|
590
606
|
logger.warning("Model-backed live review unavailable; using deterministic card: %s", exc)
|
|
591
607
|
console.print(f"[yellow]Model-backed review unavailable; using deterministic card: {exc}[/yellow]")
|
|
592
608
|
return review_turn(turn, root, client=client, task_id=task_id)
|
|
593
|
-
|
|
609
|
+
# task_id flows through so the LLM review is grounded in the task's recorded
|
|
610
|
+
# verification state (like the deterministic path), instead of judging the
|
|
611
|
+
# agent's prose in a vacuum.
|
|
612
|
+
card = await LiveReviewService(router).review(turn, root, client=client, use_llm=True, task_id=task_id)
|
|
594
613
|
return card.model_copy(update={"task_id": task_id}) if task_id else card
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
from devcouncil.utils.json_persist import dump_json
|
|
2
|
+
import logging
|
|
2
3
|
import typer
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from rich.console import Console
|
|
5
6
|
|
|
6
7
|
from devcouncil.cli.commands.init import initialize_project
|
|
7
8
|
from devcouncil.execution.fs_watcher import FilesystemWatcher
|
|
9
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
8
10
|
|
|
9
11
|
console = Console()
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
def watch_fs(
|
|
@@ -17,24 +20,32 @@ def watch_fs(
|
|
|
17
20
|
json_format: bool = typer.Option(False, "--json"),
|
|
18
21
|
):
|
|
19
22
|
root = project_root.expanduser().resolve()
|
|
23
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
24
|
+
set_log_dir(root)
|
|
25
|
+
logger.info("dev watch-fs: task=%s once=%s", task_id, once)
|
|
20
26
|
initialize_project(root, quiet=True)
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.print(f"[cyan]{event['path']}[/cyan] {status}: {event['reason']}")
|
|
28
|
+
with log_stage("watch_fs", project_root=root, task_id=task_id, once=once):
|
|
29
|
+
log_step("watch_fs/1: starting filesystem watcher", project_root=root, task_id=task_id, trace=True)
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
def _print_event(event: dict) -> None:
|
|
32
|
+
status = "allowed" if event["allowed"] else "denied"
|
|
33
|
+
console.print(f"[cyan]{event['path']}[/cyan] {status}: {event['reason']}")
|
|
34
|
+
|
|
35
|
+
watcher = FilesystemWatcher(root, task_id, poll_interval=poll_interval, on_event=_print_event)
|
|
36
|
+
if once:
|
|
37
|
+
events = watcher.scan_once()
|
|
38
|
+
if json_format:
|
|
39
|
+
typer.echo(dump_json({"events": events}, indent=2))
|
|
40
|
+
else:
|
|
41
|
+
for event in events:
|
|
42
|
+
status = "allowed" if event["allowed"] else "denied"
|
|
43
|
+
console.print(f"[cyan]{event['path']}[/cyan] {status}: {event['reason']}")
|
|
44
|
+
log_step("watch_fs/complete", project_root=root, task_id=task_id, count=len(events), trace=True)
|
|
45
|
+
return
|
|
46
|
+
console.print(f"[cyan]Watching filesystem for task {task_id}. Ctrl+C to stop.[/cyan]")
|
|
47
|
+
try:
|
|
48
|
+
watcher.watch()
|
|
49
|
+
except KeyboardInterrupt:
|
|
50
|
+
console.print("[yellow]Stopped filesystem watcher.[/yellow]")
|
|
51
|
+
log_step("watch_fs/complete", project_root=root, task_id=task_id, trace=True)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""`dev wiki` — generate and maintain the agent-facing codebase wiki.
|
|
2
|
+
|
|
3
|
+
An OpenWiki-style, self-maintaining wiki of this repository, emitted as an OKF bundle
|
|
4
|
+
under ``.devcouncil/knowledge/okf/wiki/`` so pages are automatically injected into
|
|
5
|
+
planning/council/task prompts as knowledge sources (and are exportable/renderable with
|
|
6
|
+
the existing ``dev okf`` tooling).
|
|
7
|
+
|
|
8
|
+
* ``dev wiki update`` (alias ``generate``) — build the wiki, or refresh only the pages
|
|
9
|
+
whose repo-map slice changed. With a configured model, new/stale pages are enriched
|
|
10
|
+
with LLM-written prose by the ``wiki_writer`` role; without one, the deterministic
|
|
11
|
+
skeleton is used.
|
|
12
|
+
* ``dev wiki status`` — show which pages are fresh vs stale against the current map.
|
|
13
|
+
* ``dev wiki install-action`` — write a GitHub Action that keeps the wiki updated by
|
|
14
|
+
opening a PR when the map drifts (the OpenWiki maintenance pattern).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
|
|
26
|
+
from devcouncil.indexing.repo_mapper import RepoMap
|
|
27
|
+
|
|
28
|
+
app = typer.Typer(help="Generate and maintain the agent-facing codebase wiki (OKF bundle).")
|
|
29
|
+
console = Console()
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
_WORKFLOW_PATH = Path(".github") / "workflows" / "devcouncil-wiki-update.yml"
|
|
33
|
+
|
|
34
|
+
_WIKI_WORKFLOW = """\
|
|
35
|
+
# DevCouncil codebase wiki — automatic maintenance.
|
|
36
|
+
#
|
|
37
|
+
# Generated by `dev wiki install-action`. Nightly (and on demand) this rebuilds the
|
|
38
|
+
# repository map, refreshes the stale wiki pages, and opens a PR when anything changed —
|
|
39
|
+
# so the agent-facing documentation tracks the code without anyone remembering to run it.
|
|
40
|
+
#
|
|
41
|
+
# The refresh runs with --no-llm by default (deterministic, no secrets needed). To get
|
|
42
|
+
# LLM-enriched prose in CI, add your provider API key as a repository secret and export
|
|
43
|
+
# it in the "Update wiki" step (e.g. OPENROUTER_API_KEY).
|
|
44
|
+
name: DevCouncil Wiki Update
|
|
45
|
+
|
|
46
|
+
on:
|
|
47
|
+
workflow_dispatch:
|
|
48
|
+
schedule:
|
|
49
|
+
- cron: "0 5 * * *" # nightly (UTC); adjust or remove as needed
|
|
50
|
+
|
|
51
|
+
permissions:
|
|
52
|
+
contents: write
|
|
53
|
+
pull-requests: write
|
|
54
|
+
|
|
55
|
+
jobs:
|
|
56
|
+
update-wiki:
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- uses: astral-sh/setup-uv@v7
|
|
61
|
+
- name: Install DevCouncil
|
|
62
|
+
run: |
|
|
63
|
+
uv tool install devcouncil
|
|
64
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
65
|
+
- name: Update wiki
|
|
66
|
+
run: |
|
|
67
|
+
dev map --no-wiki > /dev/null
|
|
68
|
+
dev wiki update --no-llm
|
|
69
|
+
- name: Open PR if the wiki changed
|
|
70
|
+
uses: peter-evans/create-pull-request@v7
|
|
71
|
+
with:
|
|
72
|
+
commit-message: "docs: refresh DevCouncil codebase wiki"
|
|
73
|
+
title: "Refresh DevCouncil codebase wiki"
|
|
74
|
+
body: >
|
|
75
|
+
Automated wiki refresh: the repository map drifted from the last generated
|
|
76
|
+
wiki. Review the page diffs and merge to keep agent-facing docs current.
|
|
77
|
+
branch: devcouncil/wiki-update
|
|
78
|
+
add-paths: |
|
|
79
|
+
.devcouncil/knowledge/okf/wiki/**
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# .gitignore rules that make the wiki committable in repos that ignore .devcouncil/*
|
|
83
|
+
# (the default DevCouncil layout). Appended by `dev wiki install-action` when needed.
|
|
84
|
+
_GITIGNORE_UNIGNORE = """\
|
|
85
|
+
|
|
86
|
+
# DevCouncil codebase wiki: shared knowledge-as-code, maintained by `dev wiki update`.
|
|
87
|
+
!.devcouncil/knowledge/
|
|
88
|
+
.devcouncil/knowledge/*
|
|
89
|
+
!.devcouncil/knowledge/okf/
|
|
90
|
+
.devcouncil/knowledge/okf/*
|
|
91
|
+
!.devcouncil/knowledge/okf/wiki/
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def wiki_dir_for(root: Path) -> Path:
|
|
96
|
+
from devcouncil.knowledge.wiki import wiki_dir_for as _wiki_dir_for
|
|
97
|
+
|
|
98
|
+
return _wiki_dir_for(root)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _load_repo_map(root: Path, *, remap: bool) -> RepoMap:
|
|
102
|
+
"""Load .devcouncil/repo_map.json, (re)generating it when missing or on --remap."""
|
|
103
|
+
map_path = root / ".devcouncil" / "repo_map.json"
|
|
104
|
+
if remap or not map_path.is_file():
|
|
105
|
+
from devcouncil.indexing.map_artifacts import generate_map_artifacts
|
|
106
|
+
|
|
107
|
+
console.print("[dim]Building repository map...[/dim]")
|
|
108
|
+
return generate_map_artifacts(root, map_path)
|
|
109
|
+
return RepoMap.model_validate_json(map_path.read_text(encoding="utf-8"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _print_result(result) -> None:
|
|
113
|
+
if result.changed:
|
|
114
|
+
console.print(
|
|
115
|
+
f"[green]Wiki updated:[/green] {len(result.created)} created, "
|
|
116
|
+
f"{len(result.updated)} updated, {len(result.skipped)} unchanged "
|
|
117
|
+
f"([cyan]{result.wiki_dir}[/cyan])."
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
console.print(
|
|
121
|
+
f"[green]Wiki is up to date[/green] ({len(result.skipped)} pages, "
|
|
122
|
+
f"[cyan]{result.wiki_dir}[/cyan])."
|
|
123
|
+
)
|
|
124
|
+
if result.enriched:
|
|
125
|
+
console.print(f"[green]LLM-enriched {len(result.enriched)} page(s).[/green]")
|
|
126
|
+
if result.problems:
|
|
127
|
+
console.print(f"[yellow]{len(result.problems)} OKF validation issue(s):[/yellow]")
|
|
128
|
+
for p in result.problems[:10]:
|
|
129
|
+
console.print(f" - {p}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _update(
|
|
133
|
+
project_root: Path,
|
|
134
|
+
*,
|
|
135
|
+
llm: bool,
|
|
136
|
+
force: bool,
|
|
137
|
+
remap: bool,
|
|
138
|
+
) -> None:
|
|
139
|
+
root = project_root.expanduser().resolve()
|
|
140
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
141
|
+
|
|
142
|
+
set_log_dir(root)
|
|
143
|
+
logger.info("dev wiki update: llm=%s force=%s remap=%s", llm, force, remap)
|
|
144
|
+
from devcouncil.cli.commands.init import initialize_project
|
|
145
|
+
|
|
146
|
+
initialize_project(root, quiet=True)
|
|
147
|
+
|
|
148
|
+
from devcouncil.knowledge.wiki import refresh_wiki
|
|
149
|
+
|
|
150
|
+
result = refresh_wiki(root, llm=llm, force=force, remap=remap)
|
|
151
|
+
_print_result(result)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.command("update")
|
|
155
|
+
def update(
|
|
156
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
157
|
+
llm: bool = typer.Option(True, "--llm/--no-llm", help="Enrich new/stale pages with LLM-written prose (degrades to the skeleton without a configured model)."),
|
|
158
|
+
force: bool = typer.Option(False, "--force", help="Rewrite (and re-enrich) every page, ignoring fingerprints."),
|
|
159
|
+
remap: bool = typer.Option(False, "--remap", help="Rebuild .devcouncil/repo_map.json first."),
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Create the wiki, or refresh only the pages whose repo-map slice changed."""
|
|
162
|
+
_update(project_root, llm=llm, force=force, remap=remap)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@app.command("generate")
|
|
166
|
+
def generate(
|
|
167
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
168
|
+
llm: bool = typer.Option(True, "--llm/--no-llm", help="Enrich pages with LLM-written prose."),
|
|
169
|
+
force: bool = typer.Option(False, "--force", help="Rewrite (and re-enrich) every page."),
|
|
170
|
+
remap: bool = typer.Option(False, "--remap", help="Rebuild .devcouncil/repo_map.json first."),
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Alias of `dev wiki update`."""
|
|
173
|
+
_update(project_root, llm=llm, force=force, remap=remap)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.command("read")
|
|
177
|
+
def read(
|
|
178
|
+
page: str | None = typer.Option(None, "--page", help="Fetch one wiki page by relative path."),
|
|
179
|
+
query: str | None = typer.Option(None, "--query", help="Search wiki pages by keyword."),
|
|
180
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
181
|
+
json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Read the generated codebase wiki (list, fetch, or search)."""
|
|
184
|
+
from devcouncil.knowledge.wiki_read import read_wiki_page
|
|
185
|
+
|
|
186
|
+
root = project_root.expanduser().resolve()
|
|
187
|
+
payload = read_wiki_page(root, page=page, query=query)
|
|
188
|
+
if json_output:
|
|
189
|
+
console.print_json(data=payload)
|
|
190
|
+
if not payload.get("ok", True):
|
|
191
|
+
raise typer.Exit(code=1)
|
|
192
|
+
return
|
|
193
|
+
if not payload.get("ok", True):
|
|
194
|
+
console.print(f"[red]{payload.get('error')}[/red]")
|
|
195
|
+
raise typer.Exit(code=1)
|
|
196
|
+
if "pages" in payload:
|
|
197
|
+
pages = payload["pages"]
|
|
198
|
+
if isinstance(pages, list):
|
|
199
|
+
for item in pages:
|
|
200
|
+
if isinstance(item, dict):
|
|
201
|
+
console.print(f"- {item.get('page')}: {item.get('title')}")
|
|
202
|
+
return
|
|
203
|
+
console.print(payload.get("body") or "")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.command("status")
|
|
207
|
+
def status(
|
|
208
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
209
|
+
json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Show which wiki pages are fresh vs stale against the current repository map."""
|
|
212
|
+
root = project_root.expanduser().resolve()
|
|
213
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
214
|
+
|
|
215
|
+
set_log_dir(root)
|
|
216
|
+
|
|
217
|
+
wiki_dir = wiki_dir_for(root)
|
|
218
|
+
if not (wiki_dir / "index.md").is_file():
|
|
219
|
+
if json_output:
|
|
220
|
+
console.print_json(data={"exists": False, "wiki_dir": str(wiki_dir)})
|
|
221
|
+
else:
|
|
222
|
+
console.print(
|
|
223
|
+
f"[yellow]No wiki found at[/yellow] {wiki_dir}. Run [bold]dev wiki update[/bold] to create it."
|
|
224
|
+
)
|
|
225
|
+
raise typer.Exit(code=1)
|
|
226
|
+
|
|
227
|
+
repo_map = _load_repo_map(root, remap=False)
|
|
228
|
+
from devcouncil.knowledge.wiki import _page_fingerprints, wiki_stale_pages
|
|
229
|
+
|
|
230
|
+
stale = wiki_stale_pages(root, repo_map, wiki_dir)
|
|
231
|
+
total = len(_page_fingerprints(repo_map))
|
|
232
|
+
|
|
233
|
+
if json_output:
|
|
234
|
+
console.print_json(data={
|
|
235
|
+
"exists": True,
|
|
236
|
+
"wiki_dir": str(wiki_dir),
|
|
237
|
+
"pages": total,
|
|
238
|
+
"stale": stale,
|
|
239
|
+
"fresh": total - len(stale),
|
|
240
|
+
})
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
if not stale:
|
|
244
|
+
console.print(f"[green]✓ Wiki is fresh[/green] — {total} page(s) match the current repo map.")
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
table = Table(title=f"Stale wiki pages ({len(stale)}/{total})")
|
|
248
|
+
table.add_column("Page", overflow="fold")
|
|
249
|
+
table.add_column("Reason")
|
|
250
|
+
for rel_path, reason in sorted(stale.items()):
|
|
251
|
+
table.add_row(rel_path, reason)
|
|
252
|
+
console.print(table)
|
|
253
|
+
console.print("Run [bold]dev wiki update[/bold] to refresh them.")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@app.command("install-action")
|
|
257
|
+
def install_action(
|
|
258
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
259
|
+
force: bool = typer.Option(False, "--force", help="Overwrite an existing workflow file."),
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Install a GitHub Action that keeps the wiki updated via automated PRs."""
|
|
262
|
+
root = project_root.expanduser().resolve()
|
|
263
|
+
target = root / _WORKFLOW_PATH
|
|
264
|
+
if target.exists() and not force:
|
|
265
|
+
console.print(f"[yellow]{target} already exists; use --force to overwrite.[/yellow]")
|
|
266
|
+
raise typer.Exit(code=1)
|
|
267
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
target.write_text(_WIKI_WORKFLOW, encoding="utf-8")
|
|
269
|
+
console.print(f"[green]Wrote[/green] {target}. The wiki will refresh nightly via PR.")
|
|
270
|
+
|
|
271
|
+
# The default DevCouncil .gitignore ignores .devcouncil/* — un-ignore the wiki so
|
|
272
|
+
# the workflow's PRs can actually commit it.
|
|
273
|
+
gitignore = root / ".gitignore"
|
|
274
|
+
if gitignore.is_file():
|
|
275
|
+
content = gitignore.read_text(encoding="utf-8")
|
|
276
|
+
if ".devcouncil" in content and "!.devcouncil/knowledge/okf/wiki/" not in content:
|
|
277
|
+
gitignore.write_text(content.rstrip("\n") + "\n" + _GITIGNORE_UNIGNORE, encoding="utf-8")
|
|
278
|
+
console.print("[green]Updated .gitignore[/green] so the wiki is committable.")
|