Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/repository-intelligence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Repository Intelligence CI
on:
pull_request:
push:
branches: ["feat/repository-intelligence-v1", "feat/repository-intelligence-v2", "feat/repository-intelligence-v3"]
branches: ["feat/repository-intelligence-v1", "feat/repository-intelligence-v2", "feat/repository-intelligence-v3", "feat/governed-developer-os-v1"]
permissions:
contents: read
jobs:
Expand All @@ -20,3 +20,5 @@ jobs:
- run: repo-intel impact /tmp/repository-graph.json README.md --out /tmp/change-impact.json
- run: repo-intel openhands-scope /tmp/repository-graph.json /tmp/change-impact.json --operation repo.read --out /tmp/openhands-scope.json
- run: python -c 'import json; d=json.load(open("/tmp/openhands-scope.json")); assert d["execution_authorized"] is False'
- run: repo-intel developer-plan /tmp/repository-graph.json /tmp/change-impact.json --change-request 'CI deterministic developer plan' --out /tmp/developer-plan.json
- run: python -c 'import json; d=json.load(open("/tmp/developer-plan.json")); assert d["execution_authorized"] is False and d["invariants"]["patch_requires_hpl_authority"] is True'
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This repository is being evolved from file-dump prompting scripts into a deterministic repository-intelligence engine for governed software development.

## v3 capabilities
## v3 capabilities + Phase N Developer OS v1

- SHA-256 repository file inventory;
- Python symbol/import/call graphs;
Expand Down Expand Up @@ -38,3 +38,17 @@ cross-language semantic relationships remain outside v3 coverage.
## Legacy scripts

`base_print.py` and `base_print_ai_model.py` remain preserved as historical utilities.


## Governed Developer OS v1

```bash
repo-intel developer-plan repository-graph.json change-impact.json \
--change-request "Fix the requested behavior" \
--out developer-plan.json
```

The generated plan is proposal-only. It composes bounded `repo.read`,
`test.execute`, and `repo.patch` scopes, requires retesting after mutation,
requires repository-intelligence recomputation, and requires final evidence.
The patch stage remains subject to separate HPL execution authority.
34 changes: 34 additions & 0 deletions docs/GOVERNED_DEVELOPER_OS_V1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Governed Developer OS v1

Phase N v1 composes Repository Intelligence and the already certified OpenHands
capabilities into a deterministic proposal-only developer plan.

The lifecycle is:

```text
change request
-> repository graph
-> change impact
-> repo.read scope
-> baseline test scope
-> repo.patch scope
-> retest scope
-> repository intelligence recompute
-> evidence finalization
```

The planner does not call OpenHands and does not mint HPL authority.

Every generated plan records:

```text
authority_semantics = proposal_plan_only
execution_authorized = false
```

The patch stage explicitly requires HPL execution authority. A run is not
reconciled unless every stage has a receipt, every stage succeeded, and the patch
receipt proves an execution token was present.

v1 is orchestration-contract infrastructure. It does not yet perform cross-repo
execution against the governed kernel.
3 changes: 3 additions & 0 deletions repo_intelligence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from .graph import build_repository_graph
from .impact import analyze_change_impact
from .scope import build_openhands_scope
from .developer_os import build_developer_plan, reconcile_developer_run

__all__=[
"build_repository_graph",
"analyze_change_impact",
"build_openhands_scope",
"build_developer_plan",
"reconcile_developer_run",
"load_architecture_contract",
"validate_architecture_contract",
]
13 changes: 12 additions & 1 deletion repo_intelligence/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .graph import build_repository_graph
from .impact import analyze_change_impact
from .scope import build_openhands_scope
from .developer_os import build_developer_plan


def _write(path:str,data:dict)->None:
Expand Down Expand Up @@ -34,6 +35,12 @@ def main()->None:
s.add_argument("--operation",required=True,choices=["repo.read","test.execute","repo.patch"])
s.add_argument("--out",default="openhands-scope.json")

d=sub.add_parser("developer-plan")
d.add_argument("graph")
d.add_argument("impact")
d.add_argument("--change-request",required=True)
d.add_argument("--out",default="developer-plan.json")

a=p.parse_args()

if a.cmd=="graph":
Expand All @@ -45,10 +52,14 @@ def main()->None:
elif a.cmd=="impact":
graph=json.loads(Path(a.graph).read_text(encoding="utf-8"))
data=analyze_change_impact(graph,a.changed,a.depth)
else:
elif a.cmd=="openhands-scope":
graph=json.loads(Path(a.graph).read_text(encoding="utf-8"))
impact=json.loads(Path(a.impact).read_text(encoding="utf-8"))
data=build_openhands_scope(graph,impact,a.operation)
else:
graph=json.loads(Path(a.graph).read_text(encoding="utf-8"))
impact=json.loads(Path(a.impact).read_text(encoding="utf-8"))
data=build_developer_plan(graph,impact,a.change_request)

_write(a.out,data)

Expand Down
138 changes: 138 additions & 0 deletions repo_intelligence/developer_os.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""Deterministic governed Developer OS planning primitives.

This module composes repository intelligence into a proposal-only developer
execution plan. It never performs repository IO through OpenHands and never
mints execution authority; HPL must separately admit each consequential effect.
"""
from __future__ import annotations

import hashlib
import json

from .scope import build_openhands_scope


STAGES=("repo.read","test.execute","repo.patch","test.execute")


def _canon(v:object)->str:
return json.dumps(v,sort_keys=True,separators=(",",":"),ensure_ascii=False)


def _sha(v:object)->str:
return "sha256:"+hashlib.sha256(_canon(v).encode("utf-8")).hexdigest()


def build_developer_plan(graph:dict,impact:dict,change_request:str)->dict:
if not isinstance(change_request,str) or not change_request.strip():
raise ValueError("change_request must be non-empty text")
if impact.get("unknown_paths"):
raise ValueError("cannot build developer plan with unknown changed paths")

read_scope=build_openhands_scope(graph,impact,"repo.read")
test_scope=build_openhands_scope(graph,impact,"test.execute")
patch_scope=build_openhands_scope(graph,impact,"repo.patch")

stages=[
{
"ordinal":0,
"name":"inspect",
"capability":"repo.read",
"scope_sha256":read_scope["scope_sha256"],
"requires_execution_authority":False,
"must_reconcile":True,
},
{
"ordinal":1,
"name":"baseline_tests",
"capability":"test.execute",
"scope_sha256":test_scope["scope_sha256"],
"requires_execution_authority":False,
"must_reconcile":True,
},
{
"ordinal":2,
"name":"patch",
"capability":"repo.patch",
"scope_sha256":patch_scope["scope_sha256"],
"requires_execution_authority":True,
"must_reconcile":True,
},
{
"ordinal":3,
"name":"retest",
"capability":"test.execute",
"scope_sha256":test_scope["scope_sha256"],
"requires_execution_authority":False,
"must_reconcile":True,
},
{
"ordinal":4,
"name":"recompute_repository_intelligence",
"capability":"repository.recompute",
"scope_sha256":None,
"requires_execution_authority":False,
"must_reconcile":True,
},
{
"ordinal":5,
"name":"evidence_finalize",
"capability":"evidence.finalize",
"scope_sha256":None,
"requires_execution_authority":False,
"must_reconcile":True,
},
]

core={
"schema_version":"1.0",
"authority_semantics":"proposal_plan_only",
"execution_authorized":False,
"change_request_sha256":"sha256:"+hashlib.sha256(change_request.strip().encode("utf-8")).hexdigest(),
"graph_sha256":graph.get("graph_sha256"),
"impact_sha256":impact.get("impact_sha256"),
"scopes":{
"repo.read":read_scope,
"test.execute":test_scope,
"repo.patch":patch_scope,
},
"stages":stages,
"invariants":{
"patch_requires_hpl_authority":True,
"no_stage_skips_reconciliation":True,
"retest_required_after_patch":True,
"repository_intelligence_recompute_required":True,
"evidence_finalize_required":True,
},
}
core["plan_sha256"]=_sha(core)
return core


def reconcile_developer_run(plan:dict,stage_receipts:list[dict])->dict:
expected=[s["name"] for s in plan.get("stages",[])]
observed=[str(r.get("stage","")) for r in stage_receipts if isinstance(r,dict)]
missing=[stage for stage in expected if stage not in observed]
failed=[
str(r.get("stage",""))
for r in stage_receipts
if isinstance(r,dict) and r.get("ok") is not True
]
patch_receipts=[
r for r in stage_receipts
if isinstance(r,dict) and r.get("stage")=="patch"
]
patch_authorized=all(r.get("execution_token_present") is True for r in patch_receipts) if patch_receipts else False

ok=(not missing and not failed and patch_authorized)
core={
"schema_version":"1.0",
"plan_sha256":plan.get("plan_sha256"),
"observed_stages":observed,
"missing_stages":missing,
"failed_stages":failed,
"patch_execution_token_verified":patch_authorized,
"reconciled":ok,
}
core["reconciliation_sha256"]=_sha(core)
return core
72 changes: 72 additions & 0 deletions tests/test_developer_os.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from pathlib import Path
from repo_intelligence.developer_os import build_developer_plan, reconcile_developer_run
from repo_intelligence.graph import build_repository_graph
from repo_intelligence.impact import analyze_change_impact


def _fixture(tmp_path:Path):
(tmp_path/"src").mkdir()
(tmp_path/"tests").mkdir()
(tmp_path/"src"/"calc.py").write_text("def add(a,b):\n return a+b\n",encoding="utf-8")
(tmp_path/"tests"/"test_calc.py").write_text(
"from src.calc import add\n\ndef test_add():\n assert add(1,2)==3\n",
encoding="utf-8",
)
graph=build_repository_graph(tmp_path)
impact=analyze_change_impact(graph,["src/calc.py"])
return graph,impact


def test_developer_plan_is_deterministic_and_proposal_only(tmp_path:Path):
graph,impact=_fixture(tmp_path)
p1=build_developer_plan(graph,impact,"Fix calc behavior")
p2=build_developer_plan(graph,impact,"Fix calc behavior")
assert p1==p2
assert p1["authority_semantics"]=="proposal_plan_only"
assert p1["execution_authorized"] is False
patch=[s for s in p1["stages"] if s["name"]=="patch"][0]
assert patch["requires_execution_authority"] is True
assert p1["invariants"]["retest_required_after_patch"] is True
assert p1["plan_sha256"].startswith("sha256:")


def test_developer_plan_refuses_unknown_changed_paths(tmp_path:Path):
graph=build_repository_graph(tmp_path)
impact=analyze_change_impact(graph,["missing.py"])
try:
build_developer_plan(graph,impact,"Fix missing")
except ValueError:
pass
else:
raise AssertionError("expected ValueError")


def test_developer_run_reconciliation_requires_all_receipts_and_patch_token(tmp_path:Path):
graph,impact=_fixture(tmp_path)
plan=build_developer_plan(graph,impact,"Fix calc behavior")
receipts=[
{"stage":"inspect","ok":True},
{"stage":"baseline_tests","ok":True},
{"stage":"patch","ok":True,"execution_token_present":True},
{"stage":"retest","ok":True},
{"stage":"recompute_repository_intelligence","ok":True},
{"stage":"evidence_finalize","ok":True},
]
result=reconcile_developer_run(plan,receipts)
assert result["reconciled"] is True
assert result["patch_execution_token_verified"] is True


def test_reconciliation_refuses_missing_retest(tmp_path:Path):
graph,impact=_fixture(tmp_path)
plan=build_developer_plan(graph,impact,"Fix calc behavior")
receipts=[
{"stage":"inspect","ok":True},
{"stage":"baseline_tests","ok":True},
{"stage":"patch","ok":True,"execution_token_present":True},
{"stage":"recompute_repository_intelligence","ok":True},
{"stage":"evidence_finalize","ok":True},
]
result=reconcile_developer_run(plan,receipts)
assert result["reconciled"] is False
assert "retest" in result["missing_stages"]
Loading