agentforge-framework 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.
Files changed (89) hide show
  1. agentforge_framework/.claude-plugin/plugin.json +4 -0
  2. agentforge_framework/__init__.py +3 -0
  3. agentforge_framework/agents/__init__.py +92 -0
  4. agentforge_framework/agents/architect.py +146 -0
  5. agentforge_framework/agents/implementer.py +162 -0
  6. agentforge_framework/agents/orchestrator.py +588 -0
  7. agentforge_framework/agents/reviewer.py +335 -0
  8. agentforge_framework/agents/security.py +138 -0
  9. agentforge_framework/agents/tester.py +125 -0
  10. agentforge_framework/cli.py +461 -0
  11. agentforge_framework/context/__init__.py +1 -0
  12. agentforge_framework/context/extractors/__init__.py +76 -0
  13. agentforge_framework/context/extractors/base.py +47 -0
  14. agentforge_framework/context/extractors/python.py +65 -0
  15. agentforge_framework/context/extractors/sql.py +121 -0
  16. agentforge_framework/context/extractors/yaml.py +59 -0
  17. agentforge_framework/context/prompt.py +104 -0
  18. agentforge_framework/context/resolver.py +185 -0
  19. agentforge_framework/core/__init__.py +1 -0
  20. agentforge_framework/core/commands.py +170 -0
  21. agentforge_framework/core/config.py +90 -0
  22. agentforge_framework/core/contracts.py +875 -0
  23. agentforge_framework/core/gates.py +333 -0
  24. agentforge_framework/core/issues.py +697 -0
  25. agentforge_framework/core/plan_format.py +272 -0
  26. agentforge_framework/core/process.py +141 -0
  27. agentforge_framework/core/project.py +262 -0
  28. agentforge_framework/core/registry.py +455 -0
  29. agentforge_framework/core/repo.py +185 -0
  30. agentforge_framework/core/router.py +1 -0
  31. agentforge_framework/core/runtime.py +639 -0
  32. agentforge_framework/core/skills.py +255 -0
  33. agentforge_framework/core/workflow.py +215 -0
  34. agentforge_framework/plugins/__init__.py +35 -0
  35. agentforge_framework/plugins/databricks/__init__.py +86 -0
  36. agentforge_framework/plugins/pyspark/__init__.py +57 -0
  37. agentforge_framework/plugins/python/__init__.py +45 -0
  38. agentforge_framework/plugins/sql/__init__.py +377 -0
  39. agentforge_framework/providers/__init__.py +48 -0
  40. agentforge_framework/providers/base.py +248 -0
  41. agentforge_framework/providers/claude.py +159 -0
  42. agentforge_framework/providers/codex.py +139 -0
  43. agentforge_framework/skills/MANIFEST.yaml +157 -0
  44. agentforge_framework/skills/NOTICE +49 -0
  45. agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +47 -0
  46. agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  47. agentforge_framework/skills/domain-modeling/SKILL.md +74 -0
  48. agentforge_framework/skills/domain-modeling/agents/openai.yaml +3 -0
  49. agentforge_framework/skills/grill-with-docs/SKILL.md +76 -0
  50. agentforge_framework/skills/grilling/SKILL.md +28 -0
  51. agentforge_framework/skills/grilling/agents/openai.yaml +3 -0
  52. agentforge_framework/skills/to-spec/SKILL.md +75 -0
  53. agentforge_framework/skills/to-spec/agents/openai.yaml +5 -0
  54. agentforge_framework/skills/to-tickets/SKILL.md +105 -0
  55. agentforge_framework/skills/to-tickets/agents/openai.yaml +5 -0
  56. agentforge_framework/skills/unslop/SKILL.md +131 -0
  57. agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +66 -0
  58. agentforge_framework/skills/unslop/scripts/_lang.py +106 -0
  59. agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +784 -0
  60. agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +580 -0
  61. agentforge_framework/skills/unslop/scripts/calibrate_score.py +273 -0
  62. agentforge_framework/skills/unslop/scripts/check_packs.py +80 -0
  63. agentforge_framework/skills/unslop/scripts/check_suggestions.py +225 -0
  64. agentforge_framework/skills/unslop/scripts/contribute.py +373 -0
  65. agentforge_framework/skills/unslop/scripts/diff_check.py +139 -0
  66. agentforge_framework/skills/unslop/scripts/extract_constraints.py +201 -0
  67. agentforge_framework/skills/unslop/scripts/harvest_classify.py +223 -0
  68. agentforge_framework/skills/unslop/scripts/harvest_samples.py +534 -0
  69. agentforge_framework/skills/unslop/scripts/readability_metrics.py +295 -0
  70. agentforge_framework/skills/unslop/scripts/refresh_status.py +154 -0
  71. agentforge_framework/skills/unslop/scripts/silhouette_scan.py +390 -0
  72. agentforge_framework/skills/unslop/scripts/structure_scan.py +322 -0
  73. agentforge_framework/skills/unslop/scripts/suggest.py +211 -0
  74. agentforge_framework/skills/unslop/scripts/validate_preservation.py +409 -0
  75. agentforge_framework/skills/unslop/scripts/voice_card.py +496 -0
  76. agentforge_framework/skills/unslop/scripts/voice_profile.py +194 -0
  77. agentforge_framework/skills/unslop/scripts/voice_score.py +271 -0
  78. agentforge_framework/skills/unslop/scripts/wiki_sync.py +479 -0
  79. agentforge_framework/skills/write-plainly/SKILL.md +94 -0
  80. agentforge_framework/workflows/bugfix.yaml +8 -0
  81. agentforge_framework/workflows/feature.yaml +16 -0
  82. agentforge_framework/workflows/review.yaml +10 -0
  83. agentforge_framework-0.2.0.dist-info/METADATA +321 -0
  84. agentforge_framework-0.2.0.dist-info/RECORD +89 -0
  85. agentforge_framework-0.2.0.dist-info/WHEEL +5 -0
  86. agentforge_framework-0.2.0.dist-info/entry_points.txt +3 -0
  87. agentforge_framework-0.2.0.dist-info/licenses/LICENSE +202 -0
  88. agentforge_framework-0.2.0.dist-info/licenses/src/agentforge_framework/skills/NOTICE +49 -0
  89. agentforge_framework-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "agentforge",
3
+ "description": "Skills AgentForge delivers natively: its own, and the vendored bundle"
4
+ }
@@ -0,0 +1,3 @@
1
+ """AgentForge — coordination of specialized software agents through reusable workflows."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,92 @@
1
+ """Built-in AgentForge Roles.
2
+
3
+ A Role is a definition — a name, a default Model Tier, and standing instructions
4
+ (CONTEXT.md). The tiers here are ADR-0004's default table; a user overrides them
5
+ per Run on the command line, and per project in configuration once
6
+ `agentforge init` exists.
7
+
8
+ All six Roles CONTEXT.md names can run. The Architect is in no shipped Workflow
9
+ even so — most Tasks do not need a design pass, and one at `deep` on every Run
10
+ would be the most expensive default in the project.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from ..core.contracts import ModelTier, Role
16
+ from .architect import ARCHITECT, Architect
17
+ from .implementer import IMPLEMENTER, Implementer
18
+ from .orchestrator import ORCHESTRATOR
19
+ from .reviewer import REVIEWER, Reviewer
20
+ from .security import SECURITY, Security
21
+ from .tester import TESTER, Tester
22
+
23
+ #: Roles that can actually run today.
24
+ ROLES: dict[str, Role] = {
25
+ ORCHESTRATOR.name: ORCHESTRATOR,
26
+ ARCHITECT.name: ARCHITECT,
27
+ IMPLEMENTER.name: IMPLEMENTER,
28
+ TESTER.name: TESTER,
29
+ SECURITY.name: SECURITY,
30
+ REVIEWER.name: REVIEWER,
31
+ }
32
+
33
+ #: How to run each Role, keyed the same way. A runner takes a Provider and
34
+ #: exposes `run(plan=, context=, cwd=, role=, tier=)`.
35
+ #:
36
+ #: This registry is why the Workflow runtime names no Role: it looks a runner up
37
+ #: by the name the Workflow step declared. Adding a Role is an entry here and a
38
+ #: line of YAML, never an edit to the engine. The Orchestrator is absent
39
+ #: deliberately — it produces Issues rather than running inside a Workflow.
40
+ RUNNERS: dict[str, type] = {
41
+ ARCHITECT.name: Architect,
42
+ IMPLEMENTER.name: Implementer,
43
+ TESTER.name: Tester,
44
+ SECURITY.name: Security,
45
+ REVIEWER.name: Reviewer,
46
+ }
47
+
48
+ #: The full cast from CONTEXT.md, with ADR-0004's default tiers. Every name here
49
+ #: now has a runner, so the two sets are equal — the difference between them is
50
+ #: what the Orchestrator uses to recognize a reasonable-but-unavailable choice
51
+ #: and say so, and it is what makes naming a seventh Role's tier before writing
52
+ #: its runner safe. The Architect was the last name to sit in the gap.
53
+ KNOWN_TIERS: dict[str, ModelTier] = {
54
+ "orchestrator": ModelTier.DEEP,
55
+ "architect": ModelTier.DEEP,
56
+ "security": ModelTier.DEEP,
57
+ "reviewer": ModelTier.DEEP,
58
+ "implementer": ModelTier.STANDARD,
59
+ "tester": ModelTier.CHEAP,
60
+ }
61
+
62
+
63
+ class UnknownRole(LookupError):
64
+ """A Roster names a Role this version of AgentForge cannot run."""
65
+
66
+
67
+ def resolve_role(name: str) -> Role:
68
+ """Look a Role up by name, for rebuilding a Roster out of an Issue body."""
69
+ key = name.strip().lower()
70
+ if key in ROLES:
71
+ return ROLES[key]
72
+ if key in KNOWN_TIERS:
73
+ raise UnknownRole(
74
+ f"the {key!r} Role is not implemented in this version of AgentForge; "
75
+ f"runnable Roles: {', '.join(sorted(ROLES))}"
76
+ )
77
+ raise UnknownRole(f"no Role named {name!r}; available: {', '.join(sorted(ROLES))}")
78
+
79
+
80
+ __all__ = [
81
+ "ARCHITECT",
82
+ "IMPLEMENTER",
83
+ "KNOWN_TIERS",
84
+ "ORCHESTRATOR",
85
+ "REVIEWER",
86
+ "ROLES",
87
+ "RUNNERS",
88
+ "SECURITY",
89
+ "TESTER",
90
+ "UnknownRole",
91
+ "resolve_role",
92
+ ]
@@ -0,0 +1,146 @@
1
+ """The Architect: decides the shape of a change it will not make.
2
+
3
+ It runs `deep` because a design error is the expensive kind. An Implementer's
4
+ mistake is caught by a Tester; a boundary drawn in the wrong place is caught
5
+ six months later by whoever has to work across it.
6
+
7
+ It is in none of the three shipped Workflows, and that is deliberate: most
8
+ Tasks do not need a design pass, and paying `deep` for one on every Run would
9
+ be the most expensive default in the project. The Orchestrator selects it for
10
+ Tasks that are design-heavy, and a project may name it in a Workflow of its own.
11
+
12
+ What it does not do is plan. The Plan is frozen before any Agent runs
13
+ (ADR-0003), and a Role that re-planned would be a second Orchestrator with none
14
+ of the human's context. The Architect decides how the frozen Plan should be
15
+ built, and escalates when it cannot be built as described.
16
+
17
+ Know this before putting it in a Workflow: its design reaches the Run Log and
18
+ not the next Role's prompt. Every Role is handed the frozen Plan and the Context
19
+ Pack, and nothing today folds an earlier Step's result into a later Step's
20
+ invocation. So an Architect Step informs the human reading the Issue, and an
21
+ Implementer only through them. Closing that gap is a change to what a Context
22
+ Pack carries rather than a change to this Role.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from pathlib import Path
28
+
29
+ from ..context.prompt import render_context_block
30
+ from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
31
+ from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
32
+ from .implementer import render_steps
33
+
34
+ INSTRUCTIONS = """\
35
+ You are the Architect in AgentForge. The Plan below is frozen and you are not \
36
+ rewriting it. Somebody decided what to build; you decide how it should be \
37
+ shaped.
38
+
39
+ Say where the seam goes: which module owns which decision, what the interface \
40
+ between them is, and what must not leak across it. Name the approach you \
41
+ rejected and why, because the next person to read this will otherwise rediscover \
42
+ it as a good idea.
43
+
44
+ Prefer the shape the repository already uses. A design that is better in the \
45
+ abstract and unlike everything around it costs every reader afterwards.
46
+
47
+ Write no code and change no files. What you produce is a decision, recorded on \
48
+ the Issue for the human who reads it before the work is built.
49
+
50
+ If the Plan cannot be built as described -- two steps require incompatible \
51
+ designs, or a step assumes a structure the repository does not have -- escalate \
52
+ rather than designing around it. That mismatch is the Orchestrator's to fix.\
53
+ """
54
+
55
+ PROMPT = """\
56
+ {instructions}
57
+
58
+ ## The frozen Plan
59
+
60
+ {summary}
61
+
62
+ ### Steps
63
+
64
+ {steps}
65
+ {constraints}{context}
66
+ ## Working directory
67
+
68
+ {cwd}
69
+
70
+ Read the modules the Plan names and the code they reach into, so the design you \
71
+ give back fits the repository rather than a description of it.
72
+
73
+ ## Required output
74
+
75
+ End your reply with this block and nothing after it:
76
+
77
+ {result_open}
78
+ ```json
79
+ {{
80
+ "outcome": "completed",
81
+ "summary": "one line: the shape you chose",
82
+ "detail": "the design: the seam, the interfaces, what each module owns, and the approach you rejected",
83
+ "files_changed": []
84
+ }}
85
+ ```
86
+ {result_close}
87
+
88
+ Use `"outcome": "escalated"` when the Plan cannot be built as written. Name the \
89
+ step and the mismatch in `summary`.\
90
+ """
91
+
92
+ #: The Architect runs `deep`: a boundary in the wrong place outlives the Run.
93
+ ARCHITECT = Role(name="architect", tier=ModelTier.DEEP, instructions=INSTRUCTIONS)
94
+
95
+
96
+ def build_prompt(
97
+ plan: Plan,
98
+ context: ContextPack,
99
+ cwd: Path,
100
+ role: Role = ARCHITECT,
101
+ ) -> str:
102
+ constraints = ""
103
+ if plan.constraints:
104
+ constraints = "\n### Constraints\n\n" + "\n".join(f"- {c}" for c in plan.constraints) + "\n"
105
+
106
+ return PROMPT.format(
107
+ instructions=role.instructions,
108
+ summary=plan.summary.strip(),
109
+ steps=render_steps(plan),
110
+ constraints=constraints,
111
+ context=render_context_block(context),
112
+ cwd=cwd,
113
+ result_open=RESULT_OPEN,
114
+ result_close=RESULT_CLOSE,
115
+ )
116
+
117
+
118
+ class Architect:
119
+ """One Architect invocation through the shared Provider port.
120
+
121
+ No denial path: designing is reading, so ADR-0007's shut gate costs it
122
+ nothing.
123
+ """
124
+
125
+ def __init__(self, provider) -> None:
126
+ self.provider = provider
127
+
128
+ def run(
129
+ self,
130
+ *,
131
+ plan: Plan,
132
+ context: ContextPack,
133
+ cwd: Path,
134
+ role: Role = ARCHITECT,
135
+ tier: ModelTier | None = None,
136
+ ) -> AgentResult:
137
+ return self.provider.invoke(
138
+ role=role,
139
+ prompt=build_prompt(plan, context, cwd, role),
140
+ context=context,
141
+ tier=tier or role.tier,
142
+ cwd=cwd,
143
+ )
144
+
145
+
146
+ __all__ = ["ARCHITECT", "INSTRUCTIONS", "Architect", "build_prompt"]
@@ -0,0 +1,162 @@
1
+ """The Implementer: executes a Plan it did not write.
2
+
3
+ It is handed the Plan and the Context Pack and never the human's original
4
+ phrasing (ADR-0003). The pack is the reading already done for it, so that six
5
+ Roles do not each rediscover one repository — but it is a head start rather than
6
+ a boundary, and a step that needs a file the pack does not name is a step that
7
+ reads it (ADR-0010). What the Implementer does not do is re-scope: it executes
8
+ the Plan it was given and nothing adjacent to it.
9
+
10
+ Its second job is to refuse. A plan written on Monday can be wrong by Thursday,
11
+ and an Agent that improvises a correction produces confident wrong work that
12
+ looks like success all the way to the pull request. So the prompt gives it an
13
+ escalation path and the runtime treats escalation as a result rather than an
14
+ error.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from ..context.prompt import render_context_block
22
+ from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
23
+ from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
24
+
25
+ INSTRUCTIONS = """\
26
+ You are the Implementer in AgentForge. Another Role planned this work and is \
27
+ not available to answer questions.
28
+
29
+ Execute the plan below and nothing else. Do not re-scope it, do not add \
30
+ improvements it did not ask for, and do not widen it because something nearby \
31
+ looks wrong.
32
+
33
+ If the plan does not match the repository -- a file it names is gone, a step \
34
+ assumes something untrue, two steps contradict each other -- stop and escalate. \
35
+ Do not guess at what was meant. A wrong plan caught now costs a comment; a \
36
+ wrong plan improvised around costs a review.\
37
+ """
38
+
39
+ PROMPT = """\
40
+ {instructions}
41
+
42
+ ## Plan
43
+
44
+ {summary}
45
+
46
+ ### Steps
47
+
48
+ {steps}
49
+ {constraints}{context}
50
+ ## Working directory
51
+
52
+ {cwd}
53
+
54
+ You are on a branch created for this work. Commit nothing; AgentForge commits \
55
+ for you. It commits every file it already tracks that you changed, and a file it \
56
+ does not yet track only if the plan above names it or your `files_changed` does. \
57
+ List every new file you create there, or it will not reach the pull request.
58
+ {execution}
59
+ ## Required output
60
+
61
+ End your reply with this block and nothing after it:
62
+
63
+ {result_open}
64
+ ```json
65
+ {{
66
+ "outcome": "completed",
67
+ "summary": "one line stating what you changed",
68
+ "detail": "anything a reviewer needs to know",
69
+ "files_changed": ["path/one.py"]
70
+ }}
71
+ ```
72
+ {result_close}
73
+
74
+ Use `"outcome": "escalated"` instead if the plan cannot be executed as written. \
75
+ Put the specific mismatch in `summary` -- which step, and what the repository \
76
+ actually contains. Change no files when you escalate.\
77
+ """
78
+
79
+ #: The Implementer runs at `standard`: it executes a plan it did not write.
80
+ IMPLEMENTER = Role(name="implementer", tier=ModelTier.STANDARD, instructions=INSTRUCTIONS)
81
+
82
+
83
+ def render_steps(plan: Plan) -> str:
84
+ if not plan.steps:
85
+ return "_The plan carries no steps. Escalate rather than inventing them._"
86
+
87
+ lines = []
88
+ for index, step in enumerate(plan.steps, start=1):
89
+ lines.append(f"{index}. **{step.id}** — {step.intent}")
90
+ if step.files:
91
+ lines.append(" - Files: " + ", ".join(f"`{path}`" for path in step.files))
92
+ if step.acceptance:
93
+ lines.append(f" - Done when: {step.acceptance}")
94
+ return "\n".join(lines)
95
+
96
+
97
+ #: What a Role is told when ADR-0007's gate is shut. The posture itself lives in
98
+ #: the argument vector; this is only what to do on hitting it. Without it the
99
+ #: Agent does what the M1 acceptance run did -- trace the tests by hand and
100
+ #: report `completed`, which is the one failure a Run cannot detect.
101
+ DENIED_COMMANDS = """
102
+ ## Commands
103
+
104
+ You cannot run commands in this Run. You may read and edit files, and nothing \
105
+ else. If a step's acceptance criterion asks you to run something, do not \
106
+ substitute reading for running: state in `detail` which criterion you could \
107
+ not verify, and escalate if that leaves the step unfinished. Reporting \
108
+ `completed` on a criterion you could not check is worse than stopping.
109
+ """
110
+
111
+
112
+ def build_prompt(
113
+ plan: Plan,
114
+ context: ContextPack,
115
+ cwd: Path,
116
+ role: Role = IMPLEMENTER,
117
+ allow_commands: bool = False,
118
+ ) -> str:
119
+ constraints = ""
120
+ if plan.constraints:
121
+ constraints = "\n### Constraints\n\n" + "\n".join(f"- {c}" for c in plan.constraints) + "\n"
122
+
123
+ return PROMPT.format(
124
+ instructions=role.instructions,
125
+ summary=plan.summary.strip(),
126
+ steps=render_steps(plan),
127
+ constraints=constraints,
128
+ context=render_context_block(context),
129
+ cwd=cwd,
130
+ execution="" if allow_commands else DENIED_COMMANDS,
131
+ result_open=RESULT_OPEN,
132
+ result_close=RESULT_CLOSE,
133
+ )
134
+
135
+
136
+ class Implementer:
137
+ """One Provider invocation against a frozen Plan."""
138
+
139
+ def __init__(self, provider) -> None:
140
+ self.provider = provider
141
+
142
+ def run(
143
+ self,
144
+ *,
145
+ plan: Plan,
146
+ context: ContextPack,
147
+ cwd: Path,
148
+ role: Role = IMPLEMENTER,
149
+ tier: ModelTier | None = None,
150
+ ) -> AgentResult:
151
+ tier = tier or role.tier
152
+ allow_commands = getattr(self.provider, "allow_commands", False)
153
+ return self.provider.invoke(
154
+ role=role,
155
+ prompt=build_prompt(plan, context, cwd, role, allow_commands=allow_commands),
156
+ context=context,
157
+ tier=tier,
158
+ cwd=cwd,
159
+ )
160
+
161
+
162
+ __all__ = ["IMPLEMENTER", "INSTRUCTIONS", "Implementer", "build_prompt", "render_steps"]