drydock-cli 3.1.4__tar.gz → 3.1.6__tar.gz
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.
- {drydock_cli-3.1.4/drydock_cli.egg-info → drydock_cli-3.1.6}/PKG-INFO +17 -1
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/README.md +16 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/fiar.py +153 -1
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tools/__init__.py +63 -1
- {drydock_cli-3.1.4 → drydock_cli-3.1.6/drydock_cli.egg-info}/PKG-INFO +17 -1
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/pyproject.toml +1 -1
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_fiar.py +57 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/LICENSE +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/NOTICE +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/__init__.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/__main__.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/advisor.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/agent.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/budget.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/document-canvas.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/fiar-assess.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/fiar-cap.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/fiar-evidence.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/fiar-readiness.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-data.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-debug.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-finetune.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-metrics.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-rl.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/ml-train.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/nist-ai-rmf.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/nist-csf.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/cci.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/cli.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/compaction.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/config.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/detect.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/doccanvas.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/events.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/extract.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/gittools.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/graphrag.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/guards.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/mcp.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/phases.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/poam.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/progress.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/providers.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/recipes.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/recovery.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/resume.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/rmf.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/skills.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/stig.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/subagents.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/suggest.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/task_state.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tool_policy.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tool_result.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tool_select.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tool_validate.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/trajectory.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tui/app.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/tuning.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/verification.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock/web.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/setup.cfg +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_advisor.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_approval.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_back_command.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_budget.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_cci.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_compaction.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_config.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_context_limit_issue25.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_criteria_coverage.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_cycling_detection.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_degenerate_argument.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_detect.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_doccanvas.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_effort_governor.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_events.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_extract.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_gittools.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_graphrag_sqlite.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_mcp.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_model_registry.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_phases.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_poam.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_progress.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_read_index.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_recipes.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_recovery.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_recovery_config.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_recovery_integration.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_repeated_outcome.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_resume_events.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_resume_restore.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_resume_snapshot.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_rmf.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_rolling_plan.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_skills.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_sqlite_events.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_stig.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_stop.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_subagent.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_subagents.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_suggest.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_task_state.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_timeline.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_todo.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_canonical.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_policy.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_result.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_select.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tool_validate.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_trajectory.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tui.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_tuning.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_verification_gate.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_worker_subagent.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.1.4 → drydock_cli-3.1.6}/tests/test_xccdf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.6
|
|
4
4
|
Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
|
|
5
5
|
Author: Frank Bobe III
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -267,6 +267,22 @@ exports the open findings to a deterministic **eMASS POA&M CSV** — Control (fr
|
|
|
267
267
|
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
268
268
|
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
269
269
|
|
|
270
|
+
### FIAR audit-readiness (DoD financial-statement audit)
|
|
271
|
+
|
|
272
|
+
Model a Financial Improvement and Audit Readiness engagement — a seeded key-control
|
|
273
|
+
matrix per business cycle (FBWT, P2P, PP&E, INV, CIVPAY, REIM, FR, ITGC), the five FS
|
|
274
|
+
assertions, a deterministic **evidence-chain validator** (a control can't be called
|
|
275
|
+
effective on an incomplete population→sample→…→GL→assertion trace), findings (NFRs), and
|
|
276
|
+
CAPs. `python -m drydock.fiar new|controls|control|assess|reconcile|package`; skills
|
|
277
|
+
`/fiar-assess /fiar-evidence /fiar-readiness /fiar-cap`.
|
|
278
|
+
|
|
279
|
+
**KSD evidence packaging.** `fiar package <engagement> <out> --evidence-dir <dir>` assembles
|
|
280
|
+
an audit binder — `index.md` + `index.json` mapping every control → assertions → KSDs →
|
|
281
|
+
status → evidence-chain completeness → findings — and **collects the actual evidence files
|
|
282
|
+
each test cited**, zipped. Add `--redact "<term>"` (repeatable) to **red-box** names/secrets
|
|
283
|
+
in the evidence *and* the manifest for a releasable version (verified via the Document
|
|
284
|
+
Canvas); the original engagement is never touched. Also exposed as the `FiarPackage` tool.
|
|
285
|
+
|
|
270
286
|
### Document Canvas — editing documents far larger than the context window
|
|
271
287
|
|
|
272
288
|
Edit a 300-, 800-, 1000-page document the way you edit a large codebase: **search,
|
|
@@ -243,6 +243,22 @@ exports the open findings to a deterministic **eMASS POA&M CSV** — Control (fr
|
|
|
243
243
|
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
244
244
|
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
245
245
|
|
|
246
|
+
### FIAR audit-readiness (DoD financial-statement audit)
|
|
247
|
+
|
|
248
|
+
Model a Financial Improvement and Audit Readiness engagement — a seeded key-control
|
|
249
|
+
matrix per business cycle (FBWT, P2P, PP&E, INV, CIVPAY, REIM, FR, ITGC), the five FS
|
|
250
|
+
assertions, a deterministic **evidence-chain validator** (a control can't be called
|
|
251
|
+
effective on an incomplete population→sample→…→GL→assertion trace), findings (NFRs), and
|
|
252
|
+
CAPs. `python -m drydock.fiar new|controls|control|assess|reconcile|package`; skills
|
|
253
|
+
`/fiar-assess /fiar-evidence /fiar-readiness /fiar-cap`.
|
|
254
|
+
|
|
255
|
+
**KSD evidence packaging.** `fiar package <engagement> <out> --evidence-dir <dir>` assembles
|
|
256
|
+
an audit binder — `index.md` + `index.json` mapping every control → assertions → KSDs →
|
|
257
|
+
status → evidence-chain completeness → findings — and **collects the actual evidence files
|
|
258
|
+
each test cited**, zipped. Add `--redact "<term>"` (repeatable) to **red-box** names/secrets
|
|
259
|
+
in the evidence *and* the manifest for a releasable version (verified via the Document
|
|
260
|
+
Canvas); the original engagement is never touched. Also exposed as the `FiarPackage` tool.
|
|
261
|
+
|
|
246
262
|
### Document Canvas — editing documents far larger than the context window
|
|
247
263
|
|
|
248
264
|
Edit a 300-, 800-, 1000-page document the way you edit a large codebase: **search,
|
|
@@ -20,6 +20,9 @@ not render an audit judgement.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
|
+
import re
|
|
24
|
+
import shutil
|
|
25
|
+
import zipfile
|
|
23
26
|
from dataclasses import dataclass, field, asdict
|
|
24
27
|
from datetime import date
|
|
25
28
|
from pathlib import Path
|
|
@@ -466,6 +469,139 @@ def new_engagement(name: str, cycle: str, wave: int | None = None) -> Engagement
|
|
|
466
469
|
})
|
|
467
470
|
|
|
468
471
|
|
|
472
|
+
# ── KSD evidence package (audit binder) ────────────────────────────────────
|
|
473
|
+
# Assemble the Key Supporting Documents behind an engagement into a reviewable
|
|
474
|
+
# package: a manifest (control → assertion → KSDs → status → evidence chain →
|
|
475
|
+
# findings) plus the actual evidence files, zipped. Optionally red-box text
|
|
476
|
+
# evidence for a releasable version. Deterministic, stdlib only.
|
|
477
|
+
_FILENAME_RE = re.compile(r"[\w][\w./-]*\.[A-Za-z0-9]{1,6}")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _referenced_files(control: dict, evidence_dir: Path | None) -> list[str]:
|
|
481
|
+
"""Filenames named in the control's evidence / chain text that actually exist
|
|
482
|
+
in evidence_dir (so the binder collects the KSDs the test cited)."""
|
|
483
|
+
if not evidence_dir or not evidence_dir.is_dir():
|
|
484
|
+
return []
|
|
485
|
+
avail = {p.name: p for p in evidence_dir.iterdir() if p.is_file()}
|
|
486
|
+
text = " ".join([str(control.get("evidence", "")),
|
|
487
|
+
*[str(v) for v in (control.get("evidence_chain") or {}).values()]])
|
|
488
|
+
found: list[str] = []
|
|
489
|
+
for m in _FILENAME_RE.findall(text):
|
|
490
|
+
name = Path(m).name
|
|
491
|
+
if name in avail and name not in found:
|
|
492
|
+
found.append(name)
|
|
493
|
+
return found
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def build_ksd_package(engagement_path: str | Path, out_dir: str | Path, *,
|
|
497
|
+
evidence_dir: str | Path | None = None, cycle: str | None = None,
|
|
498
|
+
status: str | None = None, redact: list[str] | None = None) -> dict:
|
|
499
|
+
"""Build a KSD evidence package from an engagement. Collects the cited evidence
|
|
500
|
+
files (from evidence_dir), writes index.json + index.md, optionally red-boxes
|
|
501
|
+
text evidence, and zips the binder. Returns a summary dict."""
|
|
502
|
+
eng = Engagement.load(engagement_path)
|
|
503
|
+
out = Path(out_dir)
|
|
504
|
+
(out / "evidence").mkdir(parents=True, exist_ok=True)
|
|
505
|
+
ev_dir = Path(evidence_dir) if evidence_dir else None
|
|
506
|
+
redact_terms = [t for t in (redact or []) if t.strip()]
|
|
507
|
+
|
|
508
|
+
findings_by_control: dict[str, list] = {}
|
|
509
|
+
for f in eng._d.get("findings", []):
|
|
510
|
+
findings_by_control.setdefault(f.get("control_id", ""), []).append(f)
|
|
511
|
+
|
|
512
|
+
controls = list(eng._d.get("controls", []))
|
|
513
|
+
if cycle:
|
|
514
|
+
controls = [c for c in controls if str(c.get("cycle", "")).upper() == cycle.upper()]
|
|
515
|
+
if status:
|
|
516
|
+
controls = [c for c in controls if c.get("status") == status]
|
|
517
|
+
|
|
518
|
+
entries = []
|
|
519
|
+
collected = 0
|
|
520
|
+
redactions = []
|
|
521
|
+
for c in controls:
|
|
522
|
+
cid = c["id"]
|
|
523
|
+
copied = []
|
|
524
|
+
for name in _referenced_files(c, ev_dir):
|
|
525
|
+
dest = out / "evidence" / cid
|
|
526
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
527
|
+
dst = dest / name
|
|
528
|
+
shutil.copy(ev_dir / name, dst) # type: ignore[arg-type]
|
|
529
|
+
if redact_terms and dst.suffix.lower() in (".txt", ".md", ".markdown"):
|
|
530
|
+
from drydock import doccanvas as dc
|
|
531
|
+
fmt = "text" if dst.suffix.lower() == ".txt" else "markdown"
|
|
532
|
+
doc = dc.parse(dst.read_text("utf-8", "replace"), fmt, str(dst))
|
|
533
|
+
n = 0
|
|
534
|
+
for term in redact_terms:
|
|
535
|
+
try:
|
|
536
|
+
n += dc.redact(doc, query=term)["redacted"]
|
|
537
|
+
except dc.PatchError:
|
|
538
|
+
pass
|
|
539
|
+
if n:
|
|
540
|
+
dst.write_text(dc.render(doc), "utf-8")
|
|
541
|
+
redactions.append({"file": f"{cid}/{name}", "redacted": n})
|
|
542
|
+
copied.append(name)
|
|
543
|
+
collected += 1
|
|
544
|
+
entries.append({
|
|
545
|
+
"id": cid, "cycle": c.get("cycle"), "objective": c.get("objective"),
|
|
546
|
+
"assertions": c.get("assertions"), "status": c.get("status", "not_tested"),
|
|
547
|
+
"ksds": c.get("ksds", []), "evidence": c.get("evidence", ""),
|
|
548
|
+
"evidence_chain": validate_chain(c.get("evidence_chain")),
|
|
549
|
+
"evidence_files": copied,
|
|
550
|
+
"findings": [{"id": f.get("id"), "severity": f.get("severity"),
|
|
551
|
+
"condition": f.get("condition")} for f in findings_by_control.get(cid, [])],
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
# For a releasable package, scrub the redaction terms from the MANIFEST too
|
|
555
|
+
# (not just the collected files) so a name/secret can't leak via the binder.
|
|
556
|
+
if redact_terms:
|
|
557
|
+
marker = "[REDACTED]"
|
|
558
|
+
for e in entries:
|
|
559
|
+
for term in redact_terms:
|
|
560
|
+
e["evidence"] = e["evidence"].replace(term, marker)
|
|
561
|
+
for f in e["findings"]:
|
|
562
|
+
f["condition"] = (f.get("condition") or "").replace(term, marker)
|
|
563
|
+
|
|
564
|
+
manifest = {
|
|
565
|
+
"engagement": eng.name, "assessable_unit": eng.cycle, "wave": eng.wave,
|
|
566
|
+
"generated": date.today().isoformat(), "source": Path(engagement_path).name,
|
|
567
|
+
"control_count": len(entries), "evidence_files_collected": collected,
|
|
568
|
+
"redactions": redactions, "controls": entries,
|
|
569
|
+
}
|
|
570
|
+
(out / "index.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
571
|
+
(out / "index.md").write_text(_ksd_index_md(manifest), encoding="utf-8")
|
|
572
|
+
|
|
573
|
+
zip_path = str(out) + ".zip"
|
|
574
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
|
|
575
|
+
for p in sorted(out.rglob("*")):
|
|
576
|
+
if p.is_file():
|
|
577
|
+
z.write(p, p.relative_to(out.parent))
|
|
578
|
+
return {"package": str(out), "zip": zip_path, "controls": len(entries),
|
|
579
|
+
"files_collected": collected, "redactions": len(redactions),
|
|
580
|
+
"missing_evidence": [e["id"] for e in entries if not e["evidence_files"]]}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _ksd_index_md(m: dict) -> str:
|
|
584
|
+
lines = [f"# KSD Evidence Package — {m['engagement']}",
|
|
585
|
+
f"Assessable unit: {m['assessable_unit']} · Wave {m['wave']} · "
|
|
586
|
+
f"generated {m['generated']} from {m['source']}",
|
|
587
|
+
f"Controls: {m['control_count']} · evidence files collected: "
|
|
588
|
+
f"{m['evidence_files_collected']} · redactions: {len(m['redactions'])}", ""]
|
|
589
|
+
for e in m["controls"]:
|
|
590
|
+
lines.append(f"## {e['id']} — {e['objective']}")
|
|
591
|
+
lines.append(f"- Cycle: {e['cycle']} · Assertions: {', '.join(e['assertions'] or [])}"
|
|
592
|
+
f" · Status: **{e['status']}**")
|
|
593
|
+
lines.append(f"- KSDs required: {', '.join(e['ksds']) or '(none listed)'}")
|
|
594
|
+
ch = e["evidence_chain"]
|
|
595
|
+
lines.append(f"- Evidence chain: {'COMPLETE' if ch['complete'] else 'INCOMPLETE — missing ' + ', '.join(ch['missing'])}")
|
|
596
|
+
if e["evidence"]:
|
|
597
|
+
lines.append(f"- Evidence examined: {e['evidence']}")
|
|
598
|
+
lines.append(f"- Evidence files: {', '.join('evidence/'+e['id']+'/'+f for f in e['evidence_files']) or '⚠ NONE collected'}")
|
|
599
|
+
for f in e["findings"]:
|
|
600
|
+
lines.append(f"- FINDING {f['id']} ({f['severity']}): {f['condition']}")
|
|
601
|
+
lines.append("")
|
|
602
|
+
return "\n".join(lines)
|
|
603
|
+
|
|
604
|
+
|
|
469
605
|
# ── CLI ───────────────────────────────────────────────────────────────────
|
|
470
606
|
def _main(argv: list[str]) -> int:
|
|
471
607
|
import argparse
|
|
@@ -482,6 +618,9 @@ def _main(argv: list[str]) -> int:
|
|
|
482
618
|
q = sub.add_parser("reconcile"); q.add_argument("entity", type=float)
|
|
483
619
|
q.add_argument("source", type=float); q.add_argument("--tolerance", type=float, default=0.0)
|
|
484
620
|
q = sub.add_parser("cycles")
|
|
621
|
+
q = sub.add_parser("package"); q.add_argument("path"); q.add_argument("out")
|
|
622
|
+
q.add_argument("--evidence-dir"); q.add_argument("--cycle"); q.add_argument("--status")
|
|
623
|
+
q.add_argument("--redact", action="append", default=[])
|
|
485
624
|
a = p.parse_args(argv)
|
|
486
625
|
|
|
487
626
|
if a.cmd == "cycles":
|
|
@@ -491,10 +630,23 @@ def _main(argv: list[str]) -> int:
|
|
|
491
630
|
if a.cmd == "reconcile":
|
|
492
631
|
r = reconcile(a.entity, a.source, tolerance=a.tolerance)
|
|
493
632
|
print(json.dumps(r, indent=2)); return 0 if r["reconciled"] else 1
|
|
633
|
+
if a.cmd == "package":
|
|
634
|
+
r = build_ksd_package(a.path, a.out, evidence_dir=a.evidence_dir,
|
|
635
|
+
cycle=a.cycle, status=a.status, redact=a.redact)
|
|
636
|
+
print(f"KSD package: {r['controls']} controls, {r['files_collected']} evidence file(s)"
|
|
637
|
+
+ (f", {r['redactions']} file(s) redacted" if r["redactions"] else "")
|
|
638
|
+
+ f" → {r['package']}/ (+ {Path(r['zip']).name})")
|
|
639
|
+
if r["missing_evidence"]:
|
|
640
|
+
print(f"⚠ no evidence file collected for: {', '.join(r['missing_evidence'])}")
|
|
641
|
+
return 0
|
|
494
642
|
if a.cmd == "new":
|
|
495
643
|
eng = new_engagement(a.name, a.cycle, wave=a.wave); eng.save(a.path)
|
|
496
644
|
print(f"created {a.path}: {eng.name} / {eng.cycle} (Wave {eng.wave}) "
|
|
497
|
-
f"— {len(eng.controls)} controls")
|
|
645
|
+
f"— {len(eng.controls)} controls")
|
|
646
|
+
if not eng.controls and a.cycle.strip().upper() not in ("*", "ALL"):
|
|
647
|
+
print(f"⚠ 0 controls seeded — '{a.cycle}' is not a known cycle. Valid cycles: "
|
|
648
|
+
f"{', '.join(CYCLES)}, or ALL. (See: python -m drydock.fiar cycles)")
|
|
649
|
+
return 0
|
|
498
650
|
|
|
499
651
|
eng = Engagement.load(a.path)
|
|
500
652
|
if a.cmd == "controls":
|
|
@@ -654,6 +654,31 @@ SCHEMAS = [
|
|
|
654
654
|
"required": ["entity", "source"],
|
|
655
655
|
},
|
|
656
656
|
},
|
|
657
|
+
{
|
|
658
|
+
"name": "FiarPackage",
|
|
659
|
+
"description": (
|
|
660
|
+
"Assemble a KSD AUDIT EVIDENCE PACKAGE from a FIAR engagement: a binder "
|
|
661
|
+
"(index.md + index.json mapping each control → assertions → KSDs → status → "
|
|
662
|
+
"evidence-chain completeness → findings) plus the actual evidence files it "
|
|
663
|
+
"cited (collected from `evidence_dir`), zipped. Pass `redact` (one or a list "
|
|
664
|
+
"of terms) to red-box names/secrets in the text evidence AND the manifest for "
|
|
665
|
+
"a releasable version — the original engagement is untouched. Optionally filter "
|
|
666
|
+
"by `cycle` or `status`."
|
|
667
|
+
),
|
|
668
|
+
"input_schema": {
|
|
669
|
+
"type": "object",
|
|
670
|
+
"properties": {
|
|
671
|
+
"path": {"type": "string", "description": "Engagement JSON path."},
|
|
672
|
+
"out": {"type": "string", "description": "Output package directory (a .zip is written beside it)."},
|
|
673
|
+
"evidence_dir": {"type": "string", "description": "Directory holding the KSD evidence files to collect."},
|
|
674
|
+
"cycle": {"type": "string", "description": "Only include this cycle (e.g. FBWT)."},
|
|
675
|
+
"status": {"type": "string", "description": "Only include controls with this status."},
|
|
676
|
+
"redact": {"type": "array", "items": {"type": "string"},
|
|
677
|
+
"description": "Terms to red-box in evidence files + manifest for release."},
|
|
678
|
+
},
|
|
679
|
+
"required": ["path", "out"],
|
|
680
|
+
},
|
|
681
|
+
},
|
|
657
682
|
{
|
|
658
683
|
"name": "GraphQuery",
|
|
659
684
|
"description": (
|
|
@@ -973,6 +998,13 @@ def _file_index(lines: list[str], fp: str) -> str:
|
|
|
973
998
|
f"offset/limit (e.g. {{\"file_path\": ..., \"offset\": <line-1>, "
|
|
974
999
|
f"\"limit\": 120}}).\n"
|
|
975
1000
|
)
|
|
1001
|
+
if is_doc:
|
|
1002
|
+
head += (
|
|
1003
|
+
"To SEARCH or EDIT this document, prefer the DOCUMENT CANVAS: DocOpen, then "
|
|
1004
|
+
"DocSearch/DocRead a region and DocPatch/DocReplace/DocRedact by block id → "
|
|
1005
|
+
"DocValidate → DocCommit — it edits precisely without loading the whole file "
|
|
1006
|
+
"(don't rewrite a large document with Write/Edit).\n"
|
|
1007
|
+
)
|
|
976
1008
|
if not anchors:
|
|
977
1009
|
return head + "(no def/class/header anchors found — read ranges with offset/limit.)"
|
|
978
1010
|
body = "\n".join(anchors[:400])
|
|
@@ -2186,6 +2218,35 @@ def tool_fiarreconcile(params: dict, config: dict) -> str:
|
|
|
2186
2218
|
f"difference {r['difference']:,.2f} (tolerance {r['tolerance']:,.2f}).")
|
|
2187
2219
|
|
|
2188
2220
|
|
|
2221
|
+
def tool_fiarpackage(params: dict, config: dict) -> str:
|
|
2222
|
+
"""Assemble a KSD audit evidence package (binder + files, zipped) from an engagement."""
|
|
2223
|
+
from drydock import fiar
|
|
2224
|
+
path = _as_str_arg(params.get("path") or "")
|
|
2225
|
+
out = _as_str_arg(params.get("out") or params.get("out_dir") or "")
|
|
2226
|
+
if not path or not out:
|
|
2227
|
+
return "Error: FiarPackage needs `path` (engagement JSON) and `out` (package directory)."
|
|
2228
|
+
redact = params.get("redact")
|
|
2229
|
+
if isinstance(redact, str):
|
|
2230
|
+
redact = [redact]
|
|
2231
|
+
try:
|
|
2232
|
+
r = fiar.build_ksd_package(
|
|
2233
|
+
path, out,
|
|
2234
|
+
evidence_dir=_as_str_arg(params.get("evidence_dir") or "") or None,
|
|
2235
|
+
cycle=_as_str_arg(params.get("cycle") or "") or None,
|
|
2236
|
+
status=_as_str_arg(params.get("status") or "") or None,
|
|
2237
|
+
redact=[str(x) for x in redact] if isinstance(redact, list) else None)
|
|
2238
|
+
except FileNotFoundError:
|
|
2239
|
+
return f"Error: engagement not found: {path}"
|
|
2240
|
+
except Exception as e:
|
|
2241
|
+
return f"Error building KSD package: {e}"
|
|
2242
|
+
msg = (f"KSD package built: {r['controls']} control(s), {r['files_collected']} evidence file(s)"
|
|
2243
|
+
+ (f", {r['redactions']} file(s) red-boxed" if r["redactions"] else "")
|
|
2244
|
+
+ f" → {r['package']}/ (index.md + index.json + evidence/, zipped as {Path(r['zip']).name}).")
|
|
2245
|
+
if r["missing_evidence"]:
|
|
2246
|
+
msg += f" ⚠ no evidence file collected for: {', '.join(r['missing_evidence'])}."
|
|
2247
|
+
return msg
|
|
2248
|
+
|
|
2249
|
+
|
|
2189
2250
|
def tool_graphquery(params: dict, config: dict) -> str:
|
|
2190
2251
|
"""Traverse the RMF typed ontology graph (read-only)."""
|
|
2191
2252
|
rmf_graph, g, _ = _rmf_graph(config)
|
|
@@ -2618,6 +2679,7 @@ _TOOLS = [
|
|
|
2618
2679
|
("FiarAssess", tool_fiarassess, False),
|
|
2619
2680
|
("FiarFinding", tool_fiarfinding, False),
|
|
2620
2681
|
("FiarReconcile", tool_fiarreconcile, True),
|
|
2682
|
+
("FiarPackage", tool_fiarpackage, False),
|
|
2621
2683
|
("WebSearch", tool_websearch, True),
|
|
2622
2684
|
("WebFetch", tool_webfetch, True),
|
|
2623
2685
|
("GitStatus", tool_gitstatus, True),
|
|
@@ -2653,7 +2715,7 @@ def register_all():
|
|
|
2653
2715
|
"StigRules": tool_stigrules, "StigRule": tool_stigrule, "StigSet": tool_stigset,
|
|
2654
2716
|
"FiarControls": tool_fiarcontrols, "FiarControl": tool_fiarcontrol,
|
|
2655
2717
|
"FiarAssess": tool_fiarassess, "FiarFinding": tool_fiarfinding,
|
|
2656
|
-
"FiarReconcile": tool_fiarreconcile,
|
|
2718
|
+
"FiarReconcile": tool_fiarreconcile, "FiarPackage": tool_fiarpackage,
|
|
2657
2719
|
"WebSearch": tool_websearch, "WebFetch": tool_webfetch,
|
|
2658
2720
|
"GitStatus": tool_gitstatus, "GitDiff": tool_gitdiff,
|
|
2659
2721
|
"GitLog": tool_gitlog, "GitCommit": tool_gitcommit,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.6
|
|
4
4
|
Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
|
|
5
5
|
Author: Frank Bobe III
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -267,6 +267,22 @@ exports the open findings to a deterministic **eMASS POA&M CSV** — Control (fr
|
|
|
267
267
|
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
268
268
|
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
269
269
|
|
|
270
|
+
### FIAR audit-readiness (DoD financial-statement audit)
|
|
271
|
+
|
|
272
|
+
Model a Financial Improvement and Audit Readiness engagement — a seeded key-control
|
|
273
|
+
matrix per business cycle (FBWT, P2P, PP&E, INV, CIVPAY, REIM, FR, ITGC), the five FS
|
|
274
|
+
assertions, a deterministic **evidence-chain validator** (a control can't be called
|
|
275
|
+
effective on an incomplete population→sample→…→GL→assertion trace), findings (NFRs), and
|
|
276
|
+
CAPs. `python -m drydock.fiar new|controls|control|assess|reconcile|package`; skills
|
|
277
|
+
`/fiar-assess /fiar-evidence /fiar-readiness /fiar-cap`.
|
|
278
|
+
|
|
279
|
+
**KSD evidence packaging.** `fiar package <engagement> <out> --evidence-dir <dir>` assembles
|
|
280
|
+
an audit binder — `index.md` + `index.json` mapping every control → assertions → KSDs →
|
|
281
|
+
status → evidence-chain completeness → findings — and **collects the actual evidence files
|
|
282
|
+
each test cited**, zipped. Add `--redact "<term>"` (repeatable) to **red-box** names/secrets
|
|
283
|
+
in the evidence *and* the manifest for a releasable version (verified via the Document
|
|
284
|
+
Canvas); the original engagement is never touched. Also exposed as the `FiarPackage` tool.
|
|
285
|
+
|
|
270
286
|
### Document Canvas — editing documents far larger than the context window
|
|
271
287
|
|
|
272
288
|
Edit a 300-, 800-, 1000-page document the way you edit a large codebase: **search,
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.1.
|
|
10
|
+
version = "3.1.6"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -209,3 +209,60 @@ def test_effective_without_chain_advises(tmp_path):
|
|
|
209
209
|
def test_control_detail_shows_chain_state(tmp_path):
|
|
210
210
|
p = _mk(tmp_path)
|
|
211
211
|
assert "not traced" in tool_fiarcontrol({"path": p, "id": "FBWT-01"}, {})
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ── KSD evidence packaging ────────────────────────────────────────────────
|
|
215
|
+
def _mk_assessed(tmp_path):
|
|
216
|
+
"""Engagement with FBWT-01 effective (evidence cites recon.txt + full chain)
|
|
217
|
+
and FBWT-02 deficient with a finding; plus an evidence dir holding recon.txt."""
|
|
218
|
+
p = _mk(tmp_path)
|
|
219
|
+
ev = tmp_path / "evidence"
|
|
220
|
+
ev.mkdir()
|
|
221
|
+
(ev / "recon.txt").write_text("Recon: preparer J. Smith; GL ties to Treasury 0.00.\n")
|
|
222
|
+
e = fiar.Engagement.load(p)
|
|
223
|
+
e.assess("FBWT-01", status="effective",
|
|
224
|
+
evidence="Examined recon.txt: ties out, preparer J. Smith signed.",
|
|
225
|
+
chain={k: f"ev-{k}" for k in fiar.EVIDENCE_CHAIN})
|
|
226
|
+
e.assess("FBWT-02", status="deficient", evidence="No aging report provided.")
|
|
227
|
+
e.add_finding(control_id="FBWT-02", condition="UDO aging not maintained",
|
|
228
|
+
severity="significant_deficiency")
|
|
229
|
+
e.save(p)
|
|
230
|
+
return p, str(ev)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_ksd_package_builds_binder_and_zip(tmp_path):
|
|
234
|
+
p, ev = _mk_assessed(tmp_path)
|
|
235
|
+
out = tmp_path / "pkg"
|
|
236
|
+
r = fiar.build_ksd_package(p, out, evidence_dir=ev)
|
|
237
|
+
assert (out / "index.json").exists() and (out / "index.md").exists()
|
|
238
|
+
assert (tmp_path / "pkg.zip").exists()
|
|
239
|
+
assert (out / "evidence" / "FBWT-01" / "recon.txt").exists() # cited file collected
|
|
240
|
+
assert r["files_collected"] == 1
|
|
241
|
+
assert "FBWT-02" in r["missing_evidence"] # no file cited
|
|
242
|
+
man = json.loads((out / "index.json").read_text())
|
|
243
|
+
by_id = {c["id"]: c for c in man["controls"]}
|
|
244
|
+
assert by_id["FBWT-01"]["evidence_chain"]["complete"] is True
|
|
245
|
+
assert by_id["FBWT-02"]["evidence_chain"]["complete"] is False
|
|
246
|
+
assert by_id["FBWT-02"]["findings"][0]["severity"] == "significant_deficiency"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_ksd_package_redaction_no_leak(tmp_path):
|
|
250
|
+
p, ev = _mk_assessed(tmp_path)
|
|
251
|
+
out = tmp_path / "rel"
|
|
252
|
+
fiar.build_ksd_package(p, out, evidence_dir=ev, redact=["J. Smith"])
|
|
253
|
+
# the name must be gone from BOTH the collected file AND the manifest
|
|
254
|
+
assert "J. Smith" not in (out / "evidence" / "FBWT-01" / "recon.txt").read_text()
|
|
255
|
+
assert "J. Smith" not in (out / "index.json").read_text()
|
|
256
|
+
assert "J. Smith" not in (out / "index.md").read_text()
|
|
257
|
+
assert "[REDACTED]" in (out / "index.md").read_text()
|
|
258
|
+
# original engagement + evidence dir are untouched
|
|
259
|
+
assert "J. Smith" in fiar.Engagement.load(p).control("FBWT-01").evidence
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_ksd_package_cycle_filter(tmp_path):
|
|
263
|
+
p = _mk(tmp_path, cycle="ALL")
|
|
264
|
+
out = tmp_path / "fbwt_only"
|
|
265
|
+
r = fiar.build_ksd_package(p, out, cycle="FBWT")
|
|
266
|
+
man = json.loads((out / "index.json").read_text())
|
|
267
|
+
assert r["controls"] >= 1
|
|
268
|
+
assert all(c["cycle"] == "FBWT" for c in man["controls"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|