multi-forge 0.2.0__py3-none-any.whl
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.
- forge/__init__.py +3 -0
- forge/_extensions/agents/.gitkeep +0 -0
- forge/_extensions/commands/.gitkeep +0 -0
- forge/_extensions/skills/analyze/SKILL.md +87 -0
- forge/_extensions/skills/challenge/SKILL.md +91 -0
- forge/_extensions/skills/consensus/SKILL.md +120 -0
- forge/_extensions/skills/consensus/resources/code_consensus_evaluation.md +94 -0
- forge/_extensions/skills/consensus/resources/consensus_evaluation.md +70 -0
- forge/_extensions/skills/consensus/resources/synthesis.md +101 -0
- forge/_extensions/skills/debate/SKILL.md +116 -0
- forge/_extensions/skills/debate/resources/code_debate_evaluation.md +101 -0
- forge/_extensions/skills/debate/resources/debate_evaluation.md +90 -0
- forge/_extensions/skills/panel/SKILL.md +141 -0
- forge/_extensions/skills/panel/resources/synthesis.md +103 -0
- forge/_extensions/skills/qa/SKILL.md +704 -0
- forge/_extensions/skills/qa/resources/checklist/0-enable.md +78 -0
- forge/_extensions/skills/qa/resources/checklist/1-preflight.md +24 -0
- forge/_extensions/skills/qa/resources/checklist/10-resume.md +143 -0
- forge/_extensions/skills/qa/resources/checklist/11-config.md +150 -0
- forge/_extensions/skills/qa/resources/checklist/12-search.md +58 -0
- forge/_extensions/skills/qa/resources/checklist/13-guard.md +237 -0
- forge/_extensions/skills/qa/resources/checklist/14-workflow.md +305 -0
- forge/_extensions/skills/qa/resources/checklist/15-skills.md +155 -0
- forge/_extensions/skills/qa/resources/checklist/16-handoff.md +224 -0
- forge/_extensions/skills/qa/resources/checklist/17-info.md +50 -0
- forge/_extensions/skills/qa/resources/checklist/18-disable.md +84 -0
- forge/_extensions/skills/qa/resources/checklist/19-uninstall.md +146 -0
- forge/_extensions/skills/qa/resources/checklist/2-extensions.md +188 -0
- forge/_extensions/skills/qa/resources/checklist/20-cleanup.md +36 -0
- forge/_extensions/skills/qa/resources/checklist/3-auth.md +234 -0
- forge/_extensions/skills/qa/resources/checklist/4-proxy.md +481 -0
- forge/_extensions/skills/qa/resources/checklist/5-session.md +541 -0
- forge/_extensions/skills/qa/resources/checklist/6-hooks.md +275 -0
- forge/_extensions/skills/qa/resources/checklist/7-costs.md +309 -0
- forge/_extensions/skills/qa/resources/checklist/8-status-line.md +174 -0
- forge/_extensions/skills/qa/resources/checklist/9-direct-commands.md +146 -0
- forge/_extensions/skills/qa/resources/checklist.md +103 -0
- forge/_extensions/skills/qa/resources/report-template.md +62 -0
- forge/_extensions/skills/qa/scripts/start-container.sh +529 -0
- forge/_extensions/skills/qa/scripts/walkthrough-state.py +1137 -0
- forge/_extensions/skills/review/SKILL.md +125 -0
- forge/_extensions/skills/review/references/claude-4.6.md +474 -0
- forge/_extensions/skills/review/references/claude-4.7.md +710 -0
- forge/_extensions/skills/review/references/gemini-3.1.md +546 -0
- forge/_extensions/skills/review/references/gpt-5.5.md +490 -0
- forge/_extensions/skills/review/references/skills-writing-guide.md +1588 -0
- forge/_extensions/skills/review/resources/code-anthropic.md +160 -0
- forge/_extensions/skills/review/resources/code-gemini.md +184 -0
- forge/_extensions/skills/review/resources/code-openai.md +203 -0
- forge/_extensions/skills/review/resources/code.md +160 -0
- forge/_extensions/skills/review-docs/SKILL.md +121 -0
- forge/_extensions/skills/review-docs/resources/docs-anthropic.md +170 -0
- forge/_extensions/skills/review-docs/resources/docs-gemini.md +204 -0
- forge/_extensions/skills/review-docs/resources/docs-openai.md +231 -0
- forge/_extensions/skills/review-docs/resources/docs.md +170 -0
- forge/_extensions/skills/smoke-test/SKILL.md +27 -0
- forge/_extensions/skills/smoke-test/scripts/smoke-test.sh +118 -0
- forge/_extensions/skills/understand/SKILL.md +148 -0
- forge/_extensions/skills/understand/resources/code-anthropic.md +163 -0
- forge/_extensions/skills/understand/resources/code-gemini.md +194 -0
- forge/_extensions/skills/understand/resources/code-openai.md +181 -0
- forge/_extensions/skills/understand/resources/code.md +163 -0
- forge/_extensions/skills/understand/resources/docs-anthropic.md +177 -0
- forge/_extensions/skills/understand/resources/docs-gemini.md +202 -0
- forge/_extensions/skills/understand/resources/docs-openai.md +191 -0
- forge/_extensions/skills/understand/resources/docs.md +177 -0
- forge/_extensions/skills/walkthrough/SKILL.md +599 -0
- forge/_extensions/skills/walkthrough/resources/checklist.md +765 -0
- forge/_extensions/skills/walkthrough/scripts/run-in-repo.sh +118 -0
- forge/_extensions/skills/walkthrough/scripts/setup-test-repo.sh +198 -0
- forge/_extensions/skills/walkthrough/scripts/walkthrough-state.py +1137 -0
- forge/backend/__init__.py +174 -0
- forge/backend/adapters/__init__.py +38 -0
- forge/backend/adapters/litellm.py +158 -0
- forge/backend/creation.py +89 -0
- forge/backend/registry.py +178 -0
- forge/cli/__init__.py +16 -0
- forge/cli/auth.py +483 -0
- forge/cli/backend.py +298 -0
- forge/cli/claude.py +411 -0
- forge/cli/config_cmd.py +303 -0
- forge/cli/extensions.py +1001 -0
- forge/cli/gc.py +165 -0
- forge/cli/guard.py +1018 -0
- forge/cli/guards.py +106 -0
- forge/cli/handoff.py +110 -0
- forge/cli/hooks/__init__.py +36 -0
- forge/cli/hooks/_group.py +20 -0
- forge/cli/hooks/_helpers.py +149 -0
- forge/cli/hooks/commands.py +1677 -0
- forge/cli/hooks/direct_commands.py +1304 -0
- forge/cli/hooks/install.py +232 -0
- forge/cli/hooks/policy.py +151 -0
- forge/cli/hooks/read_hygiene.py +74 -0
- forge/cli/hooks/verification.py +370 -0
- forge/cli/logs.py +406 -0
- forge/cli/main.py +292 -0
- forge/cli/proxy.py +1821 -0
- forge/cli/proxy_costs.py +313 -0
- forge/cli/search.py +416 -0
- forge/cli/session.py +892 -0
- forge/cli/session_addendum.py +81 -0
- forge/cli/session_fork.py +750 -0
- forge/cli/session_handoff.py +141 -0
- forge/cli/session_lifecycle.py +2053 -0
- forge/cli/session_manage.py +1336 -0
- forge/cli/session_memory.py +201 -0
- forge/cli/status_line.py +1398 -0
- forge/cli/workflow.py +1964 -0
- forge/config/__init__.py +110 -0
- forge/config/dataclass_utils.py +88 -0
- forge/config/defaults/__init__.py +0 -0
- forge/config/defaults/backends/__init__.py +0 -0
- forge/config/defaults/backends/litellm.yaml +196 -0
- forge/config/defaults/templates/__init__.py +0 -0
- forge/config/defaults/templates/litellm-anthropic-local.yaml +33 -0
- forge/config/defaults/templates/litellm-anthropic.yaml +24 -0
- forge/config/defaults/templates/litellm-gemini-flash-local.yaml +37 -0
- forge/config/defaults/templates/litellm-gemini-local.yaml +32 -0
- forge/config/defaults/templates/litellm-gemini-test.yaml +34 -0
- forge/config/defaults/templates/litellm-gemini.yaml +21 -0
- forge/config/defaults/templates/litellm-openai-codex-local.yaml +36 -0
- forge/config/defaults/templates/litellm-openai-local.yaml +38 -0
- forge/config/defaults/templates/litellm-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-anthropic.yaml +23 -0
- forge/config/defaults/templates/openrouter-deepseek.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini-flash.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini.yaml +23 -0
- forge/config/defaults/templates/openrouter-glm.yaml +23 -0
- forge/config/defaults/templates/openrouter-kimi.yaml +30 -0
- forge/config/defaults/templates/openrouter-minimax.yaml +26 -0
- forge/config/defaults/templates/openrouter-openai-codex.yaml +23 -0
- forge/config/defaults/templates/openrouter-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-qwen.yaml +25 -0
- forge/config/loader.py +675 -0
- forge/config/schema.py +448 -0
- forge/core/__init__.py +5 -0
- forge/core/auth/__init__.py +67 -0
- forge/core/auth/capabilities.py +219 -0
- forge/core/auth/credentials_file.py +244 -0
- forge/core/auth/protocols.py +18 -0
- forge/core/auth/secrets.py +243 -0
- forge/core/auth/template_secrets.py +112 -0
- forge/core/data/__init__.py +5 -0
- forge/core/data/model_catalog.yaml +1522 -0
- forge/core/data/pricing.yaml +140 -0
- forge/core/data/system_prompt_addendums/__init__.py +0 -0
- forge/core/data/system_prompt_addendums/gemini.md +330 -0
- forge/core/data/system_prompt_addendums/openai.md +328 -0
- forge/core/llm/__init__.py +231 -0
- forge/core/llm/clients/__init__.py +14 -0
- forge/core/llm/clients/base.py +115 -0
- forge/core/llm/clients/litellm.py +619 -0
- forge/core/llm/clients/openai_compat.py +244 -0
- forge/core/llm/clients/openrouter.py +234 -0
- forge/core/llm/credentials.py +439 -0
- forge/core/llm/detection.py +86 -0
- forge/core/llm/errors.py +44 -0
- forge/core/llm/protocols.py +80 -0
- forge/core/llm/types.py +176 -0
- forge/core/logging.py +146 -0
- forge/core/models/__init__.py +91 -0
- forge/core/models/catalog.py +467 -0
- forge/core/models/pricing.py +165 -0
- forge/core/models/types.py +167 -0
- forge/core/naming.py +212 -0
- forge/core/ops/__init__.py +73 -0
- forge/core/ops/context.py +141 -0
- forge/core/ops/gc.py +802 -0
- forge/core/ops/proxy.py +146 -0
- forge/core/ops/resolution.py +135 -0
- forge/core/ops/session.py +344 -0
- forge/core/ops/session_context.py +548 -0
- forge/core/paths.py +38 -0
- forge/core/process.py +54 -0
- forge/core/reactive/__init__.py +38 -0
- forge/core/reactive/cost_tracking.py +300 -0
- forge/core/reactive/env.py +180 -0
- forge/core/reactive/proxy.py +78 -0
- forge/core/reactive/routing.py +622 -0
- forge/core/reactive/session_runner.py +185 -0
- forge/core/reactive/structured_output.py +62 -0
- forge/core/reactive/tagger.py +94 -0
- forge/core/reactive/throttle.py +132 -0
- forge/core/state/__init__.py +59 -0
- forge/core/state/exceptions.py +59 -0
- forge/core/state/io.py +140 -0
- forge/core/state/lock.py +99 -0
- forge/core/state/timestamps.py +60 -0
- forge/core/transcript.py +78 -0
- forge/core/typing_helpers.py +24 -0
- forge/core/workqueue/__init__.py +67 -0
- forge/core/workqueue/queue.py +552 -0
- forge/core/workqueue/types.py +63 -0
- forge/guard/__init__.py +26 -0
- forge/guard/deterministic/__init__.py +26 -0
- forge/guard/deterministic/base.py +158 -0
- forge/guard/deterministic/coding_standards.py +256 -0
- forge/guard/deterministic/registry.py +148 -0
- forge/guard/deterministic/tdd.py +171 -0
- forge/guard/engine.py +216 -0
- forge/guard/protocols.py +91 -0
- forge/guard/queries.py +96 -0
- forge/guard/semantic/__init__.py +34 -0
- forge/guard/semantic/promotion.py +18 -0
- forge/guard/semantic/supervisor.py +813 -0
- forge/guard/semantic/verdict.py +183 -0
- forge/guard/store.py +124 -0
- forge/guard/team/__init__.py +6 -0
- forge/guard/team/config.py +24 -0
- forge/guard/team/handlers.py +209 -0
- forge/guard/team/prompts.py +41 -0
- forge/guard/types.py +125 -0
- forge/guard/workflow/__init__.py +17 -0
- forge/guard/workflow/branches.py +67 -0
- forge/guard/workflow/config.py +63 -0
- forge/guard/workflow/divergence.py +113 -0
- forge/guard/workflow/policy.py +87 -0
- forge/guard/workflow/stages.py +205 -0
- forge/install/__init__.py +55 -0
- forge/install/cli.py +281 -0
- forge/install/exceptions.py +163 -0
- forge/install/hooks.py +109 -0
- forge/install/installer.py +1037 -0
- forge/install/models.py +321 -0
- forge/install/preset.py +272 -0
- forge/install/settings_merge.py +831 -0
- forge/install/tracking.py +238 -0
- forge/install/version.py +141 -0
- forge/proxy/__init__.py +0 -0
- forge/proxy/base_client.py +181 -0
- forge/proxy/client_adapter.py +476 -0
- forge/proxy/client_factory.py +531 -0
- forge/proxy/converters.py +1206 -0
- forge/proxy/cost_logger.py +132 -0
- forge/proxy/cost_tracker.py +242 -0
- forge/proxy/data_models.py +338 -0
- forge/proxy/error_hints.py +92 -0
- forge/proxy/metrics.py +222 -0
- forge/proxy/model_spec.py +158 -0
- forge/proxy/proxies.py +333 -0
- forge/proxy/proxy_identity.py +134 -0
- forge/proxy/proxy_orchestrator.py +1018 -0
- forge/proxy/proxy_startup.py +54 -0
- forge/proxy/server.py +1561 -0
- forge/proxy/utils.py +537 -0
- forge/review/__init__.py +6 -0
- forge/review/adversarial.py +111 -0
- forge/review/consensus.py +236 -0
- forge/review/engine.py +356 -0
- forge/review/models.py +437 -0
- forge/review/resources/__init__.py +5 -0
- forge/review/resources/codereview-performance.md +85 -0
- forge/review/resources/codereview-quick.md +75 -0
- forge/review/resources/codereview-security.md +92 -0
- forge/review/resources/codereview.md +85 -0
- forge/review/resources/docreview-quick.md +75 -0
- forge/review/resources/docreview.md +86 -0
- forge/review/resources/thinkdeep.md +89 -0
- forge/review/routing.py +368 -0
- forge/review/synthesis.py +73 -0
- forge/runtime_config.py +438 -0
- forge/search/__init__.py +55 -0
- forge/search/bm25_store.py +264 -0
- forge/search/content_store.py +197 -0
- forge/search/engine.py +352 -0
- forge/search/exceptions.py +51 -0
- forge/search/extractor.py +234 -0
- forge/search/index_state.py +295 -0
- forge/search/store.py +215 -0
- forge/search/tokenizer.py +24 -0
- forge/session/__init__.py +130 -0
- forge/session/active.py +339 -0
- forge/session/artifacts.py +202 -0
- forge/session/claude/__init__.py +50 -0
- forge/session/claude/cleanup.py +105 -0
- forge/session/claude/invoke.py +236 -0
- forge/session/claude/paths.py +200 -0
- forge/session/cleanup.py +216 -0
- forge/session/config.py +34 -0
- forge/session/direct_model.py +107 -0
- forge/session/effective.py +169 -0
- forge/session/exceptions.py +255 -0
- forge/session/handoff.py +881 -0
- forge/session/handoff_agent.py +544 -0
- forge/session/hooks/__init__.py +35 -0
- forge/session/hooks/models.py +73 -0
- forge/session/hooks/session_start.py +507 -0
- forge/session/identity.py +84 -0
- forge/session/index.py +553 -0
- forge/session/manager.py +1506 -0
- forge/session/models.py +572 -0
- forge/session/overrides.py +344 -0
- forge/session/plan_resolution.py +286 -0
- forge/session/prev_sessions.py +128 -0
- forge/session/store.py +431 -0
- forge/session/validation.py +47 -0
- forge/session/worktree/__init__.py +65 -0
- forge/session/worktree/cleanup.py +262 -0
- forge/session/worktree/config_copy.py +203 -0
- forge/session/worktree/create.py +332 -0
- forge/sidecar/__init__.py +29 -0
- forge/sidecar/container.py +161 -0
- forge/sidecar/docker.py +86 -0
- forge/sidecar/secrets.py +19 -0
- multi_forge-0.2.0.dist-info/METADATA +242 -0
- multi_forge-0.2.0.dist-info/RECORD +311 -0
- multi_forge-0.2.0.dist-info/WHEEL +4 -0
- multi_forge-0.2.0.dist-info/entry_points.txt +2 -0
- multi_forge-0.2.0.dist-info/licenses/LICENSE +203 -0
- multi_forge-0.2.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""``forge session memory`` -- manage designated memory docs.
|
|
2
|
+
|
|
3
|
+
Each verb operates on ``intent.memory.designated_docs`` (the list the
|
|
4
|
+
handoff agent consults at Stop time). The override engine treats lists as
|
|
5
|
+
replace-only, so these verbs do read-modify-write through the same
|
|
6
|
+
``set_session_override`` path used elsewhere, but with single-item ergonomics.
|
|
7
|
+
|
|
8
|
+
Validation mirrors the runtime checks in ``handoff_agent._validate_designated_docs``
|
|
9
|
+
so the CLI rejects the same docs the agent would silently skip.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
|
|
20
|
+
from forge.cli.session import console
|
|
21
|
+
from forge.core.ops.context import ExecutionContext
|
|
22
|
+
from forge.core.ops.session import ForgeOpError, resolve_session, set_session_override
|
|
23
|
+
from forge.session.handoff_agent import is_safe_designated_doc_path
|
|
24
|
+
from forge.session.models import DesignatedDoc
|
|
25
|
+
|
|
26
|
+
VALID_STRATEGIES = {
|
|
27
|
+
"project-state",
|
|
28
|
+
"checklist",
|
|
29
|
+
"changelog",
|
|
30
|
+
"debugging",
|
|
31
|
+
"patterns",
|
|
32
|
+
"suggested",
|
|
33
|
+
"generic",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group("memory")
|
|
38
|
+
def memory_group() -> None:
|
|
39
|
+
"""Manage designated memory docs the handoff agent maintains.
|
|
40
|
+
|
|
41
|
+
\b
|
|
42
|
+
Examples:
|
|
43
|
+
forge session memory list-docs
|
|
44
|
+
forge session memory add-doc docs/checklist.md --strategy checklist
|
|
45
|
+
forge session memory add-doc .forge/memory/suggested.md \\
|
|
46
|
+
--strategy suggested --shadows docs/coding-standards.md
|
|
47
|
+
forge session memory remove-doc docs/checklist.md
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _validate_single_doc(path: str, strategy: str, shadows: str | None, base: Path) -> None:
|
|
52
|
+
"""Raise ``click.ClickException`` if the doc is invalid."""
|
|
53
|
+
resolved_base = base.resolve()
|
|
54
|
+
reason = is_safe_designated_doc_path(path, base, resolved_base)
|
|
55
|
+
if reason:
|
|
56
|
+
raise click.ClickException(f"Invalid path: {reason}")
|
|
57
|
+
|
|
58
|
+
if shadows is not None:
|
|
59
|
+
reason = is_safe_designated_doc_path(shadows, base, resolved_base)
|
|
60
|
+
if reason:
|
|
61
|
+
raise click.ClickException(f"Invalid --shadows path: {reason}")
|
|
62
|
+
if shadows == path:
|
|
63
|
+
raise click.ClickException("--shadows path must differ from doc path")
|
|
64
|
+
|
|
65
|
+
if strategy not in VALID_STRATEGIES:
|
|
66
|
+
raise click.ClickException(f"Unknown strategy {strategy!r}. Valid: {', '.join(sorted(VALID_STRATEGIES))}")
|
|
67
|
+
|
|
68
|
+
if strategy == "suggested" and not shadows:
|
|
69
|
+
raise click.ClickException("strategy 'suggested' requires --shadows <official-path>")
|
|
70
|
+
if shadows is not None and strategy != "suggested":
|
|
71
|
+
raise click.ClickException("--shadows requires --strategy suggested")
|
|
72
|
+
if not (base / path).resolve().is_file():
|
|
73
|
+
raise click.ClickException(f"Designated doc does not exist: {path}")
|
|
74
|
+
if shadows is not None and not (base / shadows).resolve().is_file():
|
|
75
|
+
raise click.ClickException(f"--shadows doc does not exist: {shadows}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _current_docs(*, ctx: ExecutionContext, session_name: str | None) -> tuple[list[DesignatedDoc], Path]:
|
|
79
|
+
"""Return (effective designated_docs, forge_root) for the resolved session."""
|
|
80
|
+
resolved = resolve_session(ctx=ctx, session_name=session_name)
|
|
81
|
+
state = resolved.state
|
|
82
|
+
from forge.session.effective import compute_effective_intent
|
|
83
|
+
|
|
84
|
+
effective = compute_effective_intent(state)
|
|
85
|
+
docs = list(effective.memory.designated_docs) if effective.memory else []
|
|
86
|
+
forge_root_str = state.forge_root or (state.worktree.path if state.worktree else None)
|
|
87
|
+
if forge_root_str is None:
|
|
88
|
+
raise ForgeOpError("Could not resolve forge_root for session.")
|
|
89
|
+
return docs, Path(forge_root_str)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _write_docs(*, ctx: ExecutionContext, session_name: str | None, docs: list[DesignatedDoc]) -> None:
|
|
93
|
+
"""Persist docs as an override on ``memory.designated_docs``."""
|
|
94
|
+
payload = [{"path": d.path, "strategy": d.strategy, "shadows": d.shadows} for d in docs]
|
|
95
|
+
set_session_override(
|
|
96
|
+
ctx=ctx,
|
|
97
|
+
session_name=session_name,
|
|
98
|
+
key="memory.designated_docs",
|
|
99
|
+
value_str=json.dumps(payload),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@memory_group.command("list-docs")
|
|
104
|
+
@click.option("--session", "-s", "session_name", default=None, help="Target session (default: current).")
|
|
105
|
+
@click.option("--json", "as_json", is_flag=True, default=False, help="Emit JSON.")
|
|
106
|
+
def list_docs_cmd(session_name: str | None, as_json: bool) -> None:
|
|
107
|
+
"""List designated memory docs for the session."""
|
|
108
|
+
try:
|
|
109
|
+
ctx = ExecutionContext.from_cwd()
|
|
110
|
+
docs, _ = _current_docs(ctx=ctx, session_name=session_name)
|
|
111
|
+
except ForgeOpError as e:
|
|
112
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
if as_json:
|
|
116
|
+
click.echo(
|
|
117
|
+
json.dumps(
|
|
118
|
+
[{"path": d.path, "strategy": d.strategy, "shadows": d.shadows} for d in docs],
|
|
119
|
+
indent=2,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if not docs:
|
|
125
|
+
console.print("[dim]No designated memory docs configured for this session.[/dim]")
|
|
126
|
+
console.print("[dim]Tip: forge session memory add-doc <path> --strategy <s> [--shadows <official>][/dim]")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
from rich.table import Table
|
|
130
|
+
|
|
131
|
+
table = Table(show_header=True, header_style="bold")
|
|
132
|
+
table.add_column("Path", style="cyan")
|
|
133
|
+
table.add_column("Strategy")
|
|
134
|
+
table.add_column("Shadows")
|
|
135
|
+
for doc in docs:
|
|
136
|
+
table.add_row(doc.path, doc.strategy, doc.shadows or "")
|
|
137
|
+
console.print(table)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@memory_group.command("add-doc")
|
|
141
|
+
@click.argument("path")
|
|
142
|
+
@click.option(
|
|
143
|
+
"--strategy",
|
|
144
|
+
type=click.Choice(sorted(VALID_STRATEGIES)),
|
|
145
|
+
default="generic",
|
|
146
|
+
show_default=True,
|
|
147
|
+
)
|
|
148
|
+
@click.option("--shadows", default=None, help="Official doc this proposes changes for (suggested strategy).")
|
|
149
|
+
@click.option("--session", "-s", "session_name", default=None, help="Target session (default: current).")
|
|
150
|
+
def add_doc_cmd(path: str, strategy: str, shadows: str | None, session_name: str | None) -> None:
|
|
151
|
+
"""Add a designated memory doc to the session intent.
|
|
152
|
+
|
|
153
|
+
Validates path safety and strategy/shadows consistency before persisting.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
ctx = ExecutionContext.from_cwd()
|
|
157
|
+
docs, forge_root = _current_docs(ctx=ctx, session_name=session_name)
|
|
158
|
+
except ForgeOpError as e:
|
|
159
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
|
|
162
|
+
_validate_single_doc(path, strategy, shadows, forge_root)
|
|
163
|
+
|
|
164
|
+
if any(d.path == path for d in docs):
|
|
165
|
+
raise click.ClickException(f"A designated doc with path {path!r} is already configured. Remove it first.")
|
|
166
|
+
|
|
167
|
+
docs.append(DesignatedDoc(path=path, strategy=strategy, shadows=shadows))
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
_write_docs(ctx=ctx, session_name=session_name, docs=docs)
|
|
171
|
+
except ForgeOpError as e:
|
|
172
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
console.print(f"Added designated doc [cyan]{path}[/cyan] [dim](strategy={strategy})[/dim]")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@memory_group.command("remove-doc")
|
|
179
|
+
@click.argument("path")
|
|
180
|
+
@click.option("--session", "-s", "session_name", default=None, help="Target session (default: current).")
|
|
181
|
+
def remove_doc_cmd(path: str, session_name: str | None) -> None:
|
|
182
|
+
"""Remove a designated memory doc from the session intent."""
|
|
183
|
+
try:
|
|
184
|
+
ctx = ExecutionContext.from_cwd()
|
|
185
|
+
docs, _ = _current_docs(ctx=ctx, session_name=session_name)
|
|
186
|
+
except ForgeOpError as e:
|
|
187
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
|
|
190
|
+
before = len(docs)
|
|
191
|
+
docs = [d for d in docs if d.path != path]
|
|
192
|
+
if len(docs) == before:
|
|
193
|
+
raise click.ClickException(f"No designated doc with path {path!r}")
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
_write_docs(ctx=ctx, session_name=session_name, docs=docs)
|
|
197
|
+
except ForgeOpError as e:
|
|
198
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
199
|
+
sys.exit(1)
|
|
200
|
+
|
|
201
|
+
console.print(f"Removed designated doc [cyan]{path}[/cyan]")
|