devcouncil 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -33
- 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 +56 -40
- 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 +964 -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 +281 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -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 +87 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +103 -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 +69 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -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 +53 -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 +325 -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/summary.py +21 -3
- 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
|
|
|
@@ -202,23 +210,20 @@ def status(
|
|
|
202
210
|
|
|
203
211
|
if payload["pending_signal_items"]:
|
|
204
212
|
table = Table(title="Pending Agent Responses")
|
|
205
|
-
table.add_column("
|
|
213
|
+
table.add_column("ID", style="cyan")
|
|
214
|
+
table.add_column("Client")
|
|
206
215
|
table.add_column("Task")
|
|
207
|
-
table.add_column("Transcript", overflow="fold")
|
|
208
|
-
table.add_column("Review Command", overflow="fold")
|
|
209
216
|
for signal in payload["pending_signal_items"]:
|
|
210
217
|
table.add_row(
|
|
218
|
+
signal.get("id") or "",
|
|
211
219
|
signal.get("client") or "",
|
|
212
220
|
signal.get("task_id") or "(unscoped)",
|
|
213
|
-
signal.get("transcript_path") or "",
|
|
214
|
-
signal.get("review_command") or "",
|
|
215
221
|
)
|
|
216
222
|
console.print(table)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.print(command)
|
|
223
|
+
console.print(
|
|
224
|
+
"[dim]Sensitive transcript paths and review commands: "
|
|
225
|
+
"`dev watch signals` / `dev watch review`.[/dim]"
|
|
226
|
+
)
|
|
222
227
|
|
|
223
228
|
|
|
224
229
|
@app.command("resolve")
|
|
@@ -232,7 +237,7 @@ def resolve(
|
|
|
232
237
|
if status not in {"resolved", "ignored"}:
|
|
233
238
|
message = "--status must be resolved or ignored."
|
|
234
239
|
if json_format:
|
|
235
|
-
typer.echo(
|
|
240
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
236
241
|
else:
|
|
237
242
|
console.print(f"[red]{message}[/red]")
|
|
238
243
|
raise typer.Exit(code=2)
|
|
@@ -241,12 +246,12 @@ def resolve(
|
|
|
241
246
|
if card is None:
|
|
242
247
|
message = f"Critique card {card_id} not found."
|
|
243
248
|
if json_format:
|
|
244
|
-
typer.echo(
|
|
249
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
245
250
|
else:
|
|
246
251
|
console.print(f"[red]{message}[/red]")
|
|
247
252
|
raise typer.Exit(code=1)
|
|
248
253
|
if json_format:
|
|
249
|
-
typer.echo(
|
|
254
|
+
typer.echo(dump_json({"ok": True, "card": card.model_dump()}, indent=2))
|
|
250
255
|
_log_card_resolved(root, card)
|
|
251
256
|
return
|
|
252
257
|
_log_card_resolved(root, card)
|
|
@@ -265,13 +270,13 @@ def repair(
|
|
|
265
270
|
if card is None:
|
|
266
271
|
message = f"Critique card {card_id} not found."
|
|
267
272
|
if json_format:
|
|
268
|
-
typer.echo(
|
|
273
|
+
typer.echo(dump_json({"ok": False, "error": message}, indent=2))
|
|
269
274
|
else:
|
|
270
275
|
console.print(f"[red]{message}[/red]")
|
|
271
276
|
raise typer.Exit(code=1)
|
|
272
277
|
prompt = build_live_repair_prompt(root, card)
|
|
273
278
|
if json_format:
|
|
274
|
-
typer.echo(
|
|
279
|
+
typer.echo(dump_json({"ok": True, "card": card.model_dump(), "prompt": prompt}, indent=2))
|
|
275
280
|
return
|
|
276
281
|
typer.echo(prompt)
|
|
277
282
|
|
|
@@ -296,7 +301,7 @@ def repair_all(
|
|
|
296
301
|
resolved_cards = [card for card in cards if card is not None]
|
|
297
302
|
prompt = build_bulk_live_repair_prompt(root, resolved_cards)
|
|
298
303
|
if json_format:
|
|
299
|
-
typer.echo(
|
|
304
|
+
typer.echo(dump_json({
|
|
300
305
|
"ok": True,
|
|
301
306
|
"scope_task_id": summary["scope_task_id"],
|
|
302
307
|
"cards": [card.model_dump() for card in resolved_cards],
|
|
@@ -316,7 +321,7 @@ def signals(
|
|
|
316
321
|
root = project_root.expanduser().resolve()
|
|
317
322
|
found = _filtered_signals(root, client)
|
|
318
323
|
if json_format:
|
|
319
|
-
typer.echo(
|
|
324
|
+
typer.echo(dump_json({"signals": [item.model_dump() for item in found]}, indent=2))
|
|
320
325
|
return
|
|
321
326
|
table = Table(title="DevCouncil Watch Signals")
|
|
322
327
|
table.add_column("Client", style="cyan")
|
|
@@ -378,7 +383,7 @@ def pending(
|
|
|
378
383
|
|
|
379
384
|
logger.info("dev watch pending complete: %d reviewed, %d skipped", len(reviewed), len(skipped))
|
|
380
385
|
if json_format:
|
|
381
|
-
typer.echo(
|
|
386
|
+
typer.echo(dump_json({"reviewed": reviewed, "skipped": skipped}, indent=2))
|
|
382
387
|
return
|
|
383
388
|
for item in skipped:
|
|
384
389
|
console.print(f"[yellow]Skipped signal:[/yellow] {item['reason']}")
|
|
@@ -441,7 +446,7 @@ def import_transcript(
|
|
|
441
446
|
"""Normalize a transcript into DevCouncil turn records without reviewing it."""
|
|
442
447
|
turns = load_turns(transcript.expanduser().resolve(), client=client)
|
|
443
448
|
if json_format:
|
|
444
|
-
typer.echo(
|
|
449
|
+
typer.echo(dump_json({"turns": [turn.model_dump() for turn in turns]}, indent=2))
|
|
445
450
|
return
|
|
446
451
|
console.print(f"Loaded {len(turns)} turns from {transcript}")
|
|
447
452
|
|
|
@@ -492,6 +497,7 @@ def _resolve_transcript(
|
|
|
492
497
|
transcript: Path | None = None,
|
|
493
498
|
session: str | None = None,
|
|
494
499
|
latest: bool = False,
|
|
500
|
+
task_id: str | None = None,
|
|
495
501
|
) -> Path | None:
|
|
496
502
|
if transcript is not None:
|
|
497
503
|
path = transcript.expanduser()
|
|
@@ -499,6 +505,13 @@ def _resolve_transcript(
|
|
|
499
505
|
path = root / path
|
|
500
506
|
return path.resolve()
|
|
501
507
|
|
|
508
|
+
if client.lower() == "claude" and task_id:
|
|
509
|
+
from devcouncil.live.transcripts import claude_transcript_for_task
|
|
510
|
+
|
|
511
|
+
pinned = claude_transcript_for_task(root, task_id)
|
|
512
|
+
if pinned is not None:
|
|
513
|
+
return pinned
|
|
514
|
+
|
|
502
515
|
selector = "latest" if latest else session
|
|
503
516
|
if selector is None:
|
|
504
517
|
return None
|
|
@@ -590,5 +603,8 @@ async def _review_turn(turn, root: Path, client: str, use_llm: bool, task_id: st
|
|
|
590
603
|
logger.warning("Model-backed live review unavailable; using deterministic card: %s", exc)
|
|
591
604
|
console.print(f"[yellow]Model-backed review unavailable; using deterministic card: {exc}[/yellow]")
|
|
592
605
|
return review_turn(turn, root, client=client, task_id=task_id)
|
|
593
|
-
|
|
606
|
+
# task_id flows through so the LLM review is grounded in the task's recorded
|
|
607
|
+
# verification state (like the deterministic path), instead of judging the
|
|
608
|
+
# agent's prose in a vacuum.
|
|
609
|
+
card = await LiveReviewService(router).review(turn, root, client=client, use_llm=True, task_id=task_id)
|
|
594
610
|
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.")
|