sia-package 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.
- sia/__init__.py +3 -0
- sia/__main__.py +3 -0
- sia/adapters.py +136 -0
- sia/cli.py +166 -0
- sia/core.py +485 -0
- sia/policy.py +57 -0
- sia/workflow.md +32 -0
- sia_package-0.2.0.data/data/sia_policy/AGENT.md +141 -0
- sia_package-0.2.0.data/data/sia_policy/capture-interface.md +166 -0
- sia_package-0.2.0.data/data/sia_policy/questioning-and-approval.md +44 -0
- sia_package-0.2.0.data/data/sia_policy/security-gate.md +40 -0
- sia_package-0.2.0.data/data/sia_policy/subagent-task-brief.md +145 -0
- sia_package-0.2.0.data/data/sia_policy/writing-agent-md.md +79 -0
- sia_package-0.2.0.data/data/sia_policy/writing-plan.md +85 -0
- sia_package-0.2.0.data/data/sia_policy/writing-project-skills.md +44 -0
- sia_package-0.2.0.data/data/sia_policy/writing-spec.md +52 -0
- sia_package-0.2.0.dist-info/LICENSE +21 -0
- sia_package-0.2.0.dist-info/METADATA +119 -0
- sia_package-0.2.0.dist-info/RECORD +22 -0
- sia_package-0.2.0.dist-info/WHEEL +5 -0
- sia_package-0.2.0.dist-info/entry_points.txt +2 -0
- sia_package-0.2.0.dist-info/top_level.txt +1 -0
sia/__init__.py
ADDED
sia/__main__.py
ADDED
sia/adapters.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Opt-in host adapter templates.
|
|
2
|
+
|
|
3
|
+
Adapters only expose SIA's shared state packet. They intentionally contain no
|
|
4
|
+
host-specific copy of the workflow and never modify a project's root agent
|
|
5
|
+
instructions.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
MARKER = "<!-- managed-by-sia-adapter-v1 -->"
|
|
12
|
+
|
|
13
|
+
ADAPTERS: dict[str, tuple[Path, str]] = {
|
|
14
|
+
"claude": (
|
|
15
|
+
Path(".claude/skills/sia/SKILL.md"),
|
|
16
|
+
"""---
|
|
17
|
+
name: sia
|
|
18
|
+
description: Continue the SIA workflow when the user explicitly invokes /sia.
|
|
19
|
+
disable-model-invocation: true
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# SIA
|
|
23
|
+
|
|
24
|
+
<!-- managed-by-sia-adapter-v1 -->
|
|
25
|
+
Run `sia next --json` in the project root. Read `sia/AGENT.md` when SIA is
|
|
26
|
+
vendored, otherwise use the installed package's workflow contract. Follow the
|
|
27
|
+
returned stage and mode. Keep every task's native subagent run ID in SIA with
|
|
28
|
+
`sia task dispatch`; do not implement a dispatched task in the controller.
|
|
29
|
+
Other skills and plugins remain available and keep ownership of their files.
|
|
30
|
+
""",
|
|
31
|
+
),
|
|
32
|
+
"codex": (
|
|
33
|
+
Path(".agents/skills/sia/SKILL.md"),
|
|
34
|
+
"""---
|
|
35
|
+
name: sia
|
|
36
|
+
description: Continue the SIA workflow only when the user explicitly asks for SIA or invokes this skill.
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
# SIA
|
|
40
|
+
|
|
41
|
+
<!-- managed-by-sia-adapter-v1 -->
|
|
42
|
+
Run `sia next --json` in the project root and follow the returned stage and
|
|
43
|
+
mode. Read `sia/AGENT.md` if it is vendored. Use Codex native subagents for
|
|
44
|
+
independent implementation/review work and record their run IDs with
|
|
45
|
+
`sia task dispatch`. Do not replace `AGENTS.md` or interfere with other skills.
|
|
46
|
+
""",
|
|
47
|
+
),
|
|
48
|
+
"kiro": (
|
|
49
|
+
Path(".kiro/steering/sia.md"),
|
|
50
|
+
"""---
|
|
51
|
+
inclusion: manual
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
# SIA
|
|
55
|
+
|
|
56
|
+
<!-- managed-by-sia-adapter-v1 -->
|
|
57
|
+
Run `sia next --json` in the project root and follow the returned stage and
|
|
58
|
+
mode. Read `sia/AGENT.md` if it is vendored. In orchestrator mode, delegate
|
|
59
|
+
prepared tasks to Kiro subagents and persist each returned run/agent ID with
|
|
60
|
+
`sia task dispatch`. Keep BMAD, Superpowers, steering, skills, and hooks active;
|
|
61
|
+
SIA owns only `.sia/` and explicitly generated SIA artifacts.
|
|
62
|
+
""",
|
|
63
|
+
),
|
|
64
|
+
"antigravity": (
|
|
65
|
+
Path(".agents/workflows/sia.md"),
|
|
66
|
+
"""---
|
|
67
|
+
description: Continue the persistent SIA workflow for this project
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# SIA workflow
|
|
71
|
+
|
|
72
|
+
<!-- managed-by-sia-adapter-v1 -->
|
|
73
|
+
When the user invokes `/sia`, run `sia next --json` in the project root and
|
|
74
|
+
follow the returned stage and mode. Read `sia/AGENT.md` if vendored. Use
|
|
75
|
+
Antigravity's native agents for prepared implementation and review tasks, then
|
|
76
|
+
record their run IDs with `sia task dispatch`. Do not alter other `.agents/`
|
|
77
|
+
workflows, skills, or agent definitions.
|
|
78
|
+
""",
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ALIASES = {"claude-code": "claude", "openai-codex": "codex", "google-antigravity": "antigravity"}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def normalize_host(host: str) -> str:
|
|
86
|
+
value = ALIASES.get(host.lower(), host.lower())
|
|
87
|
+
if value not in ADAPTERS:
|
|
88
|
+
raise ValueError(f"unsupported host {host!r}; choose: {', '.join(ADAPTERS)}")
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _safe_target(root: Path, relative: Path, create_parent: bool) -> Path:
|
|
93
|
+
resolved_root = root.resolve()
|
|
94
|
+
candidate = root / relative
|
|
95
|
+
current = resolved_root
|
|
96
|
+
for part in relative.parent.parts:
|
|
97
|
+
current = current / part
|
|
98
|
+
if current.exists():
|
|
99
|
+
try:
|
|
100
|
+
current.resolve().relative_to(resolved_root)
|
|
101
|
+
except ValueError as exc:
|
|
102
|
+
raise PermissionError(f"adapter path escapes project through redirected directory: {current}") from exc
|
|
103
|
+
if create_parent:
|
|
104
|
+
candidate.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
if candidate.parent.exists():
|
|
106
|
+
try:
|
|
107
|
+
candidate.parent.resolve().relative_to(resolved_root)
|
|
108
|
+
candidate.resolve(strict=False).relative_to(resolved_root)
|
|
109
|
+
except ValueError as exc:
|
|
110
|
+
raise PermissionError(f"adapter target escapes project root: {candidate}") from exc
|
|
111
|
+
return candidate
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def install_adapter(root: Path, host: str) -> tuple[str, Path]:
|
|
115
|
+
host = normalize_host(host)
|
|
116
|
+
relative, content = ADAPTERS[host]
|
|
117
|
+
target = _safe_target(root, relative, create_parent=True)
|
|
118
|
+
if target.exists():
|
|
119
|
+
existing = target.read_text(encoding="utf-8")
|
|
120
|
+
if existing == content:
|
|
121
|
+
return "unchanged", target
|
|
122
|
+
raise FileExistsError(f"refusing to overwrite existing adapter: {target}")
|
|
123
|
+
target.write_text(content, encoding="utf-8")
|
|
124
|
+
return "installed", target
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def remove_adapter(root: Path, host: str) -> tuple[str, Path]:
|
|
128
|
+
host = normalize_host(host)
|
|
129
|
+
relative, content = ADAPTERS[host]
|
|
130
|
+
target = _safe_target(root, relative, create_parent=False)
|
|
131
|
+
if not target.exists():
|
|
132
|
+
return "absent", target
|
|
133
|
+
if target.read_text(encoding="utf-8") != content:
|
|
134
|
+
raise PermissionError(f"refusing to remove modified or unmanaged file: {target}")
|
|
135
|
+
target.unlink()
|
|
136
|
+
return "removed", target
|
sia/cli.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Command-line entry point for SIA."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
from .adapters import ADAPTERS, install_adapter, remove_adapter
|
|
12
|
+
from .core import MODES, Project, SiaError
|
|
13
|
+
from .policy import render_policy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def emit(value: Any, as_json: bool = False) -> None:
|
|
17
|
+
if as_json or isinstance(value, (dict, list)):
|
|
18
|
+
print(json.dumps(value, indent=2, sort_keys=True))
|
|
19
|
+
else:
|
|
20
|
+
text = str(value)
|
|
21
|
+
encoding = sys.stdout.encoding or "utf-8"
|
|
22
|
+
print(text.encode(encoding, errors="replace").decode(encoding))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parser() -> argparse.ArgumentParser:
|
|
26
|
+
root = argparse.ArgumentParser(prog="sia", description="Persistent, host-neutral SIA orchestration")
|
|
27
|
+
root.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
28
|
+
root.add_argument("--root", default=".", help="project root (default: current directory)")
|
|
29
|
+
root.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
|
30
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
31
|
+
|
|
32
|
+
init = commands.add_parser("init", help="initialize durable SIA state")
|
|
33
|
+
init.add_argument("--mode", choices=MODES, default="orchestrator")
|
|
34
|
+
init.add_argument("--force", action="store_true", help="reset workflow state but keep existing evidence files")
|
|
35
|
+
|
|
36
|
+
commands.add_parser("status", help="show persisted workflow position")
|
|
37
|
+
next_command = commands.add_parser("next", help="emit the next host-neutral action packet")
|
|
38
|
+
next_command.add_argument("--json", action="store_true", default=argparse.SUPPRESS, help="emit machine-readable JSON")
|
|
39
|
+
commands.add_parser("guide", help="print the installed workflow contract")
|
|
40
|
+
commands.add_parser("doctor", help="diagnose package and project integration")
|
|
41
|
+
|
|
42
|
+
owner = commands.add_parser("owner", help="assign a workflow stage to SIA or another framework")
|
|
43
|
+
owner.add_argument("--stage", required=True)
|
|
44
|
+
owner.add_argument("--to", required=True, dest="stage_owner")
|
|
45
|
+
|
|
46
|
+
advance = commands.add_parser("advance", help="complete the current stage after validating evidence")
|
|
47
|
+
advance.add_argument("--evidence", action="append", default=[], help="project-local artifact path; repeatable")
|
|
48
|
+
|
|
49
|
+
record = commands.add_parser("record", help="record a PASS or DEVIATION proposal outcome")
|
|
50
|
+
record.add_argument("--outcome", required=True, choices=("pass", "deviation"))
|
|
51
|
+
_event_arguments(record, deviation=False)
|
|
52
|
+
|
|
53
|
+
capture = commands.add_parser("capture", help="capture a DEVIATION using the feedback interface")
|
|
54
|
+
_event_arguments(capture, deviation=True)
|
|
55
|
+
|
|
56
|
+
commands.add_parser("convergence", help="compute persisted deviation rates")
|
|
57
|
+
|
|
58
|
+
preflight = commands.add_parser("preflight", help="load applicable active rules before work")
|
|
59
|
+
preflight.add_argument("--scope", action="append", required=True, help="file or glob; repeatable")
|
|
60
|
+
preflight.add_argument("--acknowledge", action="append", default=[], help="approved medium/high rule ID")
|
|
61
|
+
|
|
62
|
+
rule = commands.add_parser("rule", help="manage provenance-bearing feedback rules").add_subparsers(dest="rule_command", required=True)
|
|
63
|
+
add_rule = rule.add_parser("add", help="add an approved active rule")
|
|
64
|
+
add_rule.add_argument("--text", required=True)
|
|
65
|
+
add_rule.add_argument("--scope", required=True)
|
|
66
|
+
add_rule.add_argument("--severity", required=True, choices=("low", "medium", "high"))
|
|
67
|
+
add_rule.add_argument("--error-class", required=True)
|
|
68
|
+
add_rule.add_argument("--source-event", required=True)
|
|
69
|
+
retire = rule.add_parser("retire", help="retire, but do not erase, a rule")
|
|
70
|
+
retire.add_argument("--id", required=True)
|
|
71
|
+
retire.add_argument("--reason", required=True)
|
|
72
|
+
|
|
73
|
+
adapter = commands.add_parser("adapter", help="manage explicit host launchers").add_subparsers(dest="adapter_command", required=True)
|
|
74
|
+
for action in ("install", "remove"):
|
|
75
|
+
item = adapter.add_parser(action)
|
|
76
|
+
item.add_argument("--host", required=True, choices=tuple(ADAPTERS))
|
|
77
|
+
|
|
78
|
+
task = commands.add_parser("task", help="record native subagent execution evidence").add_subparsers(dest="task_command", required=True)
|
|
79
|
+
prepare = task.add_parser("prepare")
|
|
80
|
+
prepare.add_argument("--task", required=True)
|
|
81
|
+
prepare.add_argument("--brief", required=True, type=Path)
|
|
82
|
+
prepare.add_argument("--files", action="append", required=True)
|
|
83
|
+
dispatch = task.add_parser("dispatch")
|
|
84
|
+
dispatch.add_argument("--task", required=True)
|
|
85
|
+
dispatch.add_argument("--host", required=True)
|
|
86
|
+
dispatch.add_argument("--agent-id", required=True)
|
|
87
|
+
dispatch.add_argument("--native-run-id", required=True)
|
|
88
|
+
finish = task.add_parser("finish")
|
|
89
|
+
finish.add_argument("--task", required=True)
|
|
90
|
+
finish.add_argument("--report", required=True, type=Path)
|
|
91
|
+
finish.add_argument("--review", required=True, type=Path)
|
|
92
|
+
finish.add_argument("--reviewer-agent-id", required=True)
|
|
93
|
+
finish.add_argument("--review-native-run-id", required=True)
|
|
94
|
+
|
|
95
|
+
integration = commands.add_parser("integration", help="record final combined validation and review")
|
|
96
|
+
integration.add_argument("--evidence", required=True, type=Path)
|
|
97
|
+
return root
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _event_arguments(command: argparse.ArgumentParser, deviation: bool) -> None:
|
|
101
|
+
command.add_argument("--signal", required=True)
|
|
102
|
+
command.add_argument("--context", required=True)
|
|
103
|
+
command.add_argument("--severity", required=True, choices=("low", "medium", "high"))
|
|
104
|
+
command.add_argument("--error-class", required=deviation)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main(argv: list[str] | None = None) -> int:
|
|
108
|
+
args = parser().parse_args(argv)
|
|
109
|
+
project = Project(args.root)
|
|
110
|
+
try:
|
|
111
|
+
result: Any
|
|
112
|
+
if args.command == "init":
|
|
113
|
+
result = project.initialize(args.mode, args.force)
|
|
114
|
+
elif args.command == "status":
|
|
115
|
+
result = project.status()
|
|
116
|
+
elif args.command == "next":
|
|
117
|
+
result = project.next_packet()
|
|
118
|
+
elif args.command == "guide":
|
|
119
|
+
emit(render_policy(), False)
|
|
120
|
+
return 0
|
|
121
|
+
elif args.command == "doctor":
|
|
122
|
+
result = project.doctor()
|
|
123
|
+
emit(result, args.json)
|
|
124
|
+
return 0 if result["ok"] else 1
|
|
125
|
+
elif args.command == "advance":
|
|
126
|
+
result = project.advance(args.evidence)
|
|
127
|
+
elif args.command == "owner":
|
|
128
|
+
result = project.set_stage_owner(args.stage, args.stage_owner)
|
|
129
|
+
elif args.command in {"record", "capture"}:
|
|
130
|
+
outcome = args.outcome if args.command == "record" else "deviation"
|
|
131
|
+
result = project.record_event(outcome, args.signal, args.context, args.severity, args.error_class)
|
|
132
|
+
elif args.command == "convergence":
|
|
133
|
+
result = project.convergence()
|
|
134
|
+
elif args.command == "preflight":
|
|
135
|
+
result, clear = project.preflight(args.scope, set(args.acknowledge))
|
|
136
|
+
emit(result, args.json)
|
|
137
|
+
return 0 if clear else 2
|
|
138
|
+
elif args.command == "rule":
|
|
139
|
+
if args.rule_command == "add":
|
|
140
|
+
result = project.add_rule(args.text, args.scope, args.severity, args.error_class, args.source_event)
|
|
141
|
+
else:
|
|
142
|
+
result = project.retire_rule(args.id, args.reason)
|
|
143
|
+
elif args.command == "adapter":
|
|
144
|
+
action = install_adapter if args.adapter_command == "install" else remove_adapter
|
|
145
|
+
status, path = action(project.root, args.host)
|
|
146
|
+
result = {"status": status, "host": args.host, "path": str(path.relative_to(project.root))}
|
|
147
|
+
elif args.command == "task":
|
|
148
|
+
if args.task_command == "prepare":
|
|
149
|
+
result = project.prepare_task(args.task, args.brief, args.files)
|
|
150
|
+
elif args.task_command == "dispatch":
|
|
151
|
+
result = project.dispatch_task(args.task, args.host, args.agent_id, args.native_run_id)
|
|
152
|
+
else:
|
|
153
|
+
result = project.finish_task(args.task, args.report, args.review, args.reviewer_agent_id, args.review_native_run_id)
|
|
154
|
+
elif args.command == "integration":
|
|
155
|
+
result = project.record_integration(args.evidence)
|
|
156
|
+
else: # pragma: no cover - argparse prevents this
|
|
157
|
+
raise SiaError(f"unknown command: {args.command}")
|
|
158
|
+
emit(result, args.json)
|
|
159
|
+
return 0
|
|
160
|
+
except (SiaError, FileExistsError, PermissionError, ValueError) as exc:
|
|
161
|
+
print(f"sia: {exc}", file=sys.stderr)
|
|
162
|
+
return 2
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
raise SystemExit(main())
|