specjam 0.0.1__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.
Files changed (48) hide show
  1. specjam-0.0.1/MANIFEST.in +3 -0
  2. specjam-0.0.1/PKG-INFO +112 -0
  3. specjam-0.0.1/README.md +99 -0
  4. specjam-0.0.1/pyproject.toml +53 -0
  5. specjam-0.0.1/setup.cfg +4 -0
  6. specjam-0.0.1/src/specjam/__init__.py +3 -0
  7. specjam-0.0.1/src/specjam/__main__.py +6 -0
  8. specjam-0.0.1/src/specjam/archive.py +52 -0
  9. specjam-0.0.1/src/specjam/classification.py +30 -0
  10. specjam-0.0.1/src/specjam/cli.py +124 -0
  11. specjam-0.0.1/src/specjam/graph_engine.py +211 -0
  12. specjam-0.0.1/src/specjam/installer.py +280 -0
  13. specjam-0.0.1/src/specjam/model.py +149 -0
  14. specjam-0.0.1/src/specjam/payload/__init__.py +2 -0
  15. specjam-0.0.1/src/specjam/payload/bridge/AGENTS.md +79 -0
  16. specjam-0.0.1/src/specjam/payload/ignore-rules.txt +7 -0
  17. specjam-0.0.1/src/specjam/payload/workspace/WORKSPACE.md +32 -0
  18. specjam-0.0.1/src/specjam/payload/workspace/config.json +8 -0
  19. specjam-0.0.1/src/specjam/payload/workspace/graphs/delivery-graph.json +61 -0
  20. specjam-0.0.1/src/specjam/payload/workspace/graphs/discovery-graph.json +39 -0
  21. specjam-0.0.1/src/specjam/payload/workspace/graphs/postmortem-graph.json +47 -0
  22. specjam-0.0.1/src/specjam/payload/workspace/references/RWSA.md +11 -0
  23. specjam-0.0.1/src/specjam/payload/workspace/runtime/capability-matrix.md +15 -0
  24. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-bounded-review/SKILL.md +33 -0
  25. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-bounded-review/rws.json +11 -0
  26. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-daily-loop/SKILL.md +32 -0
  27. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-daily-loop/rws.json +12 -0
  28. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-flow/SKILL.md +37 -0
  29. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-flow/rws.json +13 -0
  30. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-skill-authoring/SKILL.md +36 -0
  31. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-skill-authoring/references/RWSA.md +4 -0
  32. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-skill-authoring/rws.json +13 -0
  33. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-trace-to-skill/SKILL.md +37 -0
  34. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-trace-to-skill/references/RWSA.md +4 -0
  35. specjam-0.0.1/src/specjam/payload/workspace/skills/specjam-trace-to-skill/rws.json +12 -0
  36. specjam-0.0.1/src/specjam/reviewers.py +80 -0
  37. specjam-0.0.1/src/specjam/rws.py +186 -0
  38. specjam-0.0.1/src/specjam.egg-info/PKG-INFO +112 -0
  39. specjam-0.0.1/src/specjam.egg-info/SOURCES.txt +46 -0
  40. specjam-0.0.1/src/specjam.egg-info/dependency_links.txt +1 -0
  41. specjam-0.0.1/src/specjam.egg-info/entry_points.txt +2 -0
  42. specjam-0.0.1/src/specjam.egg-info/top_level.txt +1 -0
  43. specjam-0.0.1/tests/test_archive.py +35 -0
  44. specjam-0.0.1/tests/test_classification.py +16 -0
  45. specjam-0.0.1/tests/test_graph_engine.py +104 -0
  46. specjam-0.0.1/tests/test_installer.py +58 -0
  47. specjam-0.0.1/tests/test_reviewers.py +46 -0
  48. specjam-0.0.1/tests/test_rws.py +26 -0
@@ -0,0 +1,3 @@
1
+ global-exclude *.py[cod]
2
+ global-exclude __pycache__
3
+ prune src/specjam/**/__pycache__
specjam-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: specjam
3
+ Version: 0.0.1
4
+ Summary: A harness-neutral agentic engineering workspace built around flow graphs and RWSA skills.
5
+ Author: Fernando Costa
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://github.com/nandoligeiro/SpecJam
8
+ Project-URL: Repository, https://github.com/nandoligeiro/SpecJam
9
+ Project-URL: Issues, https://github.com/nandoligeiro/SpecJam/issues
10
+ Keywords: agents,engineering-workflow,flow-graphs,rws,cli
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+
14
+ # SpecJam
15
+
16
+ SpecJam is an installable, harness-neutral agentic engineering workspace. It turns a natural-language request into a durable, auditable flow instead of allowing an agent to jump directly from ambiguity to implementation.
17
+
18
+ The first release combines three ideas:
19
+
20
+ - **Flow graphs**: declarative stages, artifact gates, conditional routing, bounded reviewers, and a pure routing function.
21
+ - **RWSA skills**: Routing, Workflow, Semantics, and Attachments as an executable contract for reusable agent capabilities.
22
+ - **A local workspace**: a standard-library-only CLI that installs, verifies, inspects, updates, and scaffolds the method in any repository.
23
+
24
+ ## Quick start
25
+
26
+ Run SpecJam directly from PyPI with `uvx`, or install it persistently with `uv tool`:
27
+
28
+ ```bash
29
+ uvx specjam install
30
+ uv tool install specjam
31
+ specjam install
32
+ ```
33
+
34
+ To pin this release explicitly:
35
+
36
+ ```bash
37
+ uvx --from 'specjam==0.0.1' specjam --help
38
+ ```
39
+
40
+ For a source checkout, `uv run` keeps the package isolated and reproducible:
41
+
42
+ ```bash
43
+ uv run specjam install
44
+ uv run specjam verify
45
+ uv run specjam inspect
46
+ uv run specjam classify "Add a new payment capability"
47
+ uv run specjam flow scaffold --flow delivery --slug payment-capability
48
+ ```
49
+
50
+ The installer creates `.specjam/` and a minimal root `AGENTS.md` bridge. Existing bridge files are preserved unless `--force` is supplied. Runtime state is ignored; the lockfile and managed definitions remain inspectable.
51
+
52
+ ## Architecture
53
+
54
+ ```text
55
+ natural language -----> discovery graph
56
+ |
57
+ +-----------------> delivery graph
58
+ |
59
+ +-----------------> postmortem graph
60
+
61
+ graph + state --> pure route --> decision --> append-only trail
62
+ |
63
+ +--> bounded read-only reviewers
64
+ |
65
+ +--> one synthesis writer
66
+ ```
67
+
68
+ The route function never writes files, invokes tools, or calls a model. Persistence belongs to the trail adapter. This split makes the highest-risk policy easy to test.
69
+
70
+ ## First-class flows
71
+
72
+ SpecJam keeps each flow explicit and data-driven:
73
+
74
+ - **Discovery** turns uncertainty into a problem statement, evidence, options, and a decision.
75
+ - **Delivery** runs SDD: `SPEC → DESIGN? → BUILD → VALIDATE`.
76
+ - **Postmortem** turns an incident record into root cause, actions, and follow-up.
77
+
78
+ The daily engineering loop is a supporting L0–L3 classification mechanism, not a fourth flow graph.
79
+
80
+ The three graphs are `graphs/discovery-graph.json`, `graphs/delivery-graph.json`, and `graphs/postmortem-graph.json`; organizations can add or replace graphs without changing the routing engine.
81
+
82
+ ## RWSA contract
83
+
84
+ The skill representation follows the routing-aware decomposition described in [Workflow-to-Skill: Skill Creation via Routing-Workflow-Semantics-Attachments Decomposition](https://arxiv.org/abs/2606.06893):
85
+
86
+ ```text
87
+ Skill = Routing + (Workflow + Semantics + Attachments)
88
+ ```
89
+
90
+ `src/specjam/rws.py` validates this contract without requiring a YAML or AI dependency. The bundled authoring skills render the contract into the portable `SKILL.md` format described by [Agent Skills](https://agentskills.io/specification).
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ uv run --no-sync python -m unittest discover -s tests -v
96
+ uv run specjam graph validate src/specjam/payload/workspace/graphs/delivery-graph.json
97
+ uv build --no-sources
98
+ ```
99
+
100
+ The release workflow builds both wheel and source distribution on a `v*` tag and publishes them through PyPI Trusted Publishing. Configure the `pypi` GitHub environment and the matching PyPI trusted publisher before pushing a release tag.
101
+
102
+ ```bash
103
+ uv version 0.0.1
104
+ uv build --no-sources
105
+ uv publish
106
+ ```
107
+
108
+ The project intentionally keeps the engine dependency-free. Packaging helpers may use the Python standard library only; third-party model, agent, tracker, and cloud integrations are extension points.
109
+
110
+ ## Status
111
+
112
+ This is the initial public foundation: graph engine, RWSA contract, reviewers, classification, installer, archive packaging, core skills, and test coverage. Organization-specific domain packs and tracker adapters stay outside the core as required by the PRD.
@@ -0,0 +1,99 @@
1
+ # SpecJam
2
+
3
+ SpecJam is an installable, harness-neutral agentic engineering workspace. It turns a natural-language request into a durable, auditable flow instead of allowing an agent to jump directly from ambiguity to implementation.
4
+
5
+ The first release combines three ideas:
6
+
7
+ - **Flow graphs**: declarative stages, artifact gates, conditional routing, bounded reviewers, and a pure routing function.
8
+ - **RWSA skills**: Routing, Workflow, Semantics, and Attachments as an executable contract for reusable agent capabilities.
9
+ - **A local workspace**: a standard-library-only CLI that installs, verifies, inspects, updates, and scaffolds the method in any repository.
10
+
11
+ ## Quick start
12
+
13
+ Run SpecJam directly from PyPI with `uvx`, or install it persistently with `uv tool`:
14
+
15
+ ```bash
16
+ uvx specjam install
17
+ uv tool install specjam
18
+ specjam install
19
+ ```
20
+
21
+ To pin this release explicitly:
22
+
23
+ ```bash
24
+ uvx --from 'specjam==0.0.1' specjam --help
25
+ ```
26
+
27
+ For a source checkout, `uv run` keeps the package isolated and reproducible:
28
+
29
+ ```bash
30
+ uv run specjam install
31
+ uv run specjam verify
32
+ uv run specjam inspect
33
+ uv run specjam classify "Add a new payment capability"
34
+ uv run specjam flow scaffold --flow delivery --slug payment-capability
35
+ ```
36
+
37
+ The installer creates `.specjam/` and a minimal root `AGENTS.md` bridge. Existing bridge files are preserved unless `--force` is supplied. Runtime state is ignored; the lockfile and managed definitions remain inspectable.
38
+
39
+ ## Architecture
40
+
41
+ ```text
42
+ natural language -----> discovery graph
43
+ |
44
+ +-----------------> delivery graph
45
+ |
46
+ +-----------------> postmortem graph
47
+
48
+ graph + state --> pure route --> decision --> append-only trail
49
+ |
50
+ +--> bounded read-only reviewers
51
+ |
52
+ +--> one synthesis writer
53
+ ```
54
+
55
+ The route function never writes files, invokes tools, or calls a model. Persistence belongs to the trail adapter. This split makes the highest-risk policy easy to test.
56
+
57
+ ## First-class flows
58
+
59
+ SpecJam keeps each flow explicit and data-driven:
60
+
61
+ - **Discovery** turns uncertainty into a problem statement, evidence, options, and a decision.
62
+ - **Delivery** runs SDD: `SPEC → DESIGN? → BUILD → VALIDATE`.
63
+ - **Postmortem** turns an incident record into root cause, actions, and follow-up.
64
+
65
+ The daily engineering loop is a supporting L0–L3 classification mechanism, not a fourth flow graph.
66
+
67
+ The three graphs are `graphs/discovery-graph.json`, `graphs/delivery-graph.json`, and `graphs/postmortem-graph.json`; organizations can add or replace graphs without changing the routing engine.
68
+
69
+ ## RWSA contract
70
+
71
+ The skill representation follows the routing-aware decomposition described in [Workflow-to-Skill: Skill Creation via Routing-Workflow-Semantics-Attachments Decomposition](https://arxiv.org/abs/2606.06893):
72
+
73
+ ```text
74
+ Skill = Routing + (Workflow + Semantics + Attachments)
75
+ ```
76
+
77
+ `src/specjam/rws.py` validates this contract without requiring a YAML or AI dependency. The bundled authoring skills render the contract into the portable `SKILL.md` format described by [Agent Skills](https://agentskills.io/specification).
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ uv run --no-sync python -m unittest discover -s tests -v
83
+ uv run specjam graph validate src/specjam/payload/workspace/graphs/delivery-graph.json
84
+ uv build --no-sources
85
+ ```
86
+
87
+ The release workflow builds both wheel and source distribution on a `v*` tag and publishes them through PyPI Trusted Publishing. Configure the `pypi` GitHub environment and the matching PyPI trusted publisher before pushing a release tag.
88
+
89
+ ```bash
90
+ uv version 0.0.1
91
+ uv build --no-sources
92
+ uv publish
93
+ ```
94
+
95
+ The project intentionally keeps the engine dependency-free. Packaging helpers may use the Python standard library only; third-party model, agent, tracker, and cloud integrations are extension points.
96
+
97
+ ## Status
98
+
99
+ This is the initial public foundation: graph engine, RWSA contract, reviewers, classification, installer, archive packaging, core skills, and test coverage. Organization-specific domain packs and tracker adapters stay outside the core as required by the PRD.
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "specjam"
7
+ version = "0.0.1"
8
+ description = "A harness-neutral agentic engineering workspace built around flow graphs and RWSA skills."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ authors = [{ name = "Fernando Costa" }]
12
+ license = "LicenseRef-Proprietary"
13
+ keywords = ["agents", "engineering-workflow", "flow-graphs", "rws", "cli"]
14
+ dependencies = []
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/nandoligeiro/SpecJam"
18
+ Repository = "https://github.com/nandoligeiro/SpecJam"
19
+ Issues = "https://github.com/nandoligeiro/SpecJam/issues"
20
+
21
+ [project.scripts]
22
+ specjam = "specjam.cli:main"
23
+
24
+ [tool.setuptools]
25
+ package-dir = {"" = "src"}
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+ include = ["specjam", "specjam.*"]
30
+ exclude = ["specjam.payload.__pycache__", "specjam.payload.__pycache__.*"]
31
+
32
+ [tool.setuptools.package-data]
33
+ "specjam.payload" = [
34
+ "ignore-rules.txt",
35
+ "bridge/*.md",
36
+ "workspace/*.md",
37
+ "workspace/*.json",
38
+ "workspace/graphs/*.json",
39
+ "workspace/references/*.md",
40
+ "workspace/runtime/*.md",
41
+ "workspace/skills/*/*.md",
42
+ "workspace/skills/*/*.json",
43
+ "workspace/skills/*/references/*.md",
44
+ ]
45
+
46
+ [tool.setuptools.exclude-package-data]
47
+ "specjam.payload" = ["**/__pycache__/*", "**/*.py[cod]"]
48
+
49
+ [tool.uv]
50
+ package = true
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """SpecJam's deterministic agentic engineering primitives."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,6 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
6
+
@@ -0,0 +1,52 @@
1
+ """Safe validation and extraction for self-contained SpecJam archives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import stat
6
+ import zipfile
7
+ from pathlib import Path
8
+ from re import match
9
+
10
+
11
+ class UnsafeArchiveError(ValueError):
12
+ """Raised before extraction when an archive entry can escape its root."""
13
+
14
+
15
+ def _validate_name(name: str) -> None:
16
+ normalized = name.replace("\\", "/")
17
+ if normalized.startswith("/") or ".." in Path(normalized).parts:
18
+ raise UnsafeArchiveError(f"unsafe archive path: {name}")
19
+ if Path(normalized).drive or match(r"^[A-Za-z]:", normalized) or normalized.startswith("//"):
20
+ raise UnsafeArchiveError(f"archive path has a drive prefix: {name}")
21
+
22
+
23
+ def validate_archive(path: str | Path) -> None:
24
+ with zipfile.ZipFile(path) as archive:
25
+ for info in archive.infolist():
26
+ _validate_name(info.filename)
27
+ mode = (info.external_attr >> 16) & 0o170000
28
+ if mode == stat.S_IFLNK:
29
+ raise UnsafeArchiveError(f"symlink archive entry: {info.filename}")
30
+
31
+
32
+ def extract_archive(path: str | Path, destination: str | Path) -> None:
33
+ """Validate every entry before writing any bytes."""
34
+
35
+ validate_archive(path)
36
+ root = Path(destination).resolve()
37
+ root.mkdir(parents=True, exist_ok=True)
38
+ with zipfile.ZipFile(path) as archive:
39
+ for info in archive.infolist():
40
+ relative = Path(info.filename.replace("\\", "/"))
41
+ target = (root / relative).resolve()
42
+ try:
43
+ target.relative_to(root)
44
+ except ValueError as exc:
45
+ raise UnsafeArchiveError(f"archive entry escapes destination: {info.filename}") from exc
46
+ if info.is_dir():
47
+ target.mkdir(parents=True, exist_ok=True)
48
+ continue
49
+ target.parent.mkdir(parents=True, exist_ok=True)
50
+ with archive.open(info) as source, target.open("wb") as sink:
51
+ while chunk := source.read(1024 * 1024):
52
+ sink.write(chunk)
@@ -0,0 +1,30 @@
1
+ """Deterministic proportional effort classification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Classification:
10
+ level: str
11
+ flow: str
12
+ reason: str
13
+
14
+ def to_dict(self) -> dict[str, str]:
15
+ return {"level": self.level, "flow": self.flow, "reason": self.reason}
16
+
17
+
18
+ def classify_request(text: str, *, ambiguous: bool = False, critical: bool = False) -> Classification:
19
+ normalized = text.strip().lower()
20
+ if not normalized:
21
+ return Classification("L3", "discovery", "empty requests are ambiguous and require discovery")
22
+ lookup_words = ("what is", "how do i", "lookup", "look up", "explain", "compare", "quantos", "qual é")
23
+ if not ambiguous and not critical and (normalized.endswith("?") or any(word in normalized for word in lookup_words)):
24
+ return Classification("L0", "daily", "lookup or explanation; no implementation specification required")
25
+ small_words = ("rename", "typo", "readme", "format", "small", "minor", "documentation")
26
+ if not ambiguous and not critical and any(word in normalized for word in small_words):
27
+ return Classification("L1", "daily", "bounded local change with proportional ceremony")
28
+ if ambiguous or critical or any(word in normalized for word in ("security", "payment", "migration", "breaking", "production")):
29
+ return Classification("L3", "discovery", "ambiguous or critical work requires discovery before delivery")
30
+ return Classification("L2", "delivery", "feature-sized work requires specification and implementation gates")
@@ -0,0 +1,124 @@
1
+ """Command-line interface for SpecJam."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .classification import classify_request
11
+ from .graph_engine import load_graph, record_route
12
+ from .installer import inspect_installation, install, scaffold_flow, update, verify
13
+ from .model import RouteState
14
+ from .rws import load_rwsa, validate_rwsa
15
+
16
+
17
+ def _emit(value) -> None:
18
+ json.dump(value, sys.stdout, indent=2, sort_keys=True)
19
+ sys.stdout.write("\n")
20
+
21
+
22
+ def _flags(values: list[str] | None) -> dict[str, bool]:
23
+ result: dict[str, bool] = {}
24
+ for value in values or []:
25
+ key, separator, raw = value.partition("=")
26
+ result[key] = raw.lower() not in {"0", "false", "no", "off"} if separator else True
27
+ return result
28
+
29
+
30
+ def build_parser() -> argparse.ArgumentParser:
31
+ parser = argparse.ArgumentParser(prog="specjam", description="Deterministic agentic engineering workspace")
32
+ commands = parser.add_subparsers(dest="command", required=True)
33
+
34
+ install_parser = commands.add_parser("install", help="install the managed workspace")
35
+ install_parser.add_argument("--target", default=".")
36
+ install_parser.add_argument("--force", action="store_true")
37
+
38
+ verify_parser = commands.add_parser("verify", help="verify managed files and lockfile")
39
+ verify_parser.add_argument("--target", default=".")
40
+
41
+ update_parser = commands.add_parser("update", help="update unchanged managed files")
42
+ update_parser.add_argument("--target", default=".")
43
+ update_parser.add_argument("--remove-stale", action="store_true")
44
+
45
+ inspect_parser = commands.add_parser("inspect", help="inspect installation metadata")
46
+ inspect_parser.add_argument("--target", default=".")
47
+
48
+ classify_parser = commands.add_parser("classify", help="classify work into L0-L3")
49
+ classify_parser.add_argument("text", nargs="+")
50
+ classify_parser.add_argument("--ambiguous", action="store_true")
51
+ classify_parser.add_argument("--critical", action="store_true")
52
+
53
+ graph = commands.add_parser("graph", help="validate a graph")
54
+ graph_commands = graph.add_subparsers(dest="graph_command", required=True)
55
+ graph_validate = graph_commands.add_parser("validate")
56
+ graph_validate.add_argument("path")
57
+
58
+ route = commands.add_parser("route", help="evaluate one graph gate")
59
+ route.add_argument("--graph", required=True)
60
+ route.add_argument("--stage", required=True)
61
+ route.add_argument("--artifact", action="append", default=[])
62
+ route.add_argument("--flag", action="append", default=[])
63
+ route.add_argument("--trail")
64
+ route.add_argument("--run-id", default="local")
65
+
66
+ rws = commands.add_parser("rws", help="validate an RWSA contract")
67
+ rws_commands = rws.add_subparsers(dest="rws_command", required=True)
68
+ rws_validate = rws_commands.add_parser("validate")
69
+ rws_validate.add_argument("path")
70
+
71
+ flow = commands.add_parser("flow", help="scaffold durable flow artifacts")
72
+ flow_commands = flow.add_subparsers(dest="flow_command", required=True)
73
+ scaffold = flow_commands.add_parser("scaffold")
74
+ scaffold.add_argument("--target", default=".")
75
+ scaffold.add_argument("--flow", required=True, choices=("discovery", "delivery", "postmortem"))
76
+ scaffold.add_argument("--slug", required=True)
77
+ return parser
78
+
79
+
80
+ def main(argv: list[str] | None = None) -> int:
81
+ args = build_parser().parse_args(argv)
82
+ if args.command == "install":
83
+ _emit(install(args.target, force=args.force).to_dict())
84
+ return 0
85
+ if args.command == "verify":
86
+ report = verify(args.target)
87
+ _emit(report.to_dict())
88
+ return 0 if not (report.missing or report.modified or report.stale) else 1
89
+ if args.command == "update":
90
+ _emit(update(args.target, remove_stale=args.remove_stale).to_dict())
91
+ return 0
92
+ if args.command == "inspect":
93
+ _emit(inspect_installation(args.target))
94
+ return 0
95
+ if args.command == "classify":
96
+ _emit(classify_request(" ".join(args.text), ambiguous=args.ambiguous, critical=args.critical).to_dict())
97
+ return 0
98
+ if args.command == "graph" and args.graph_command == "validate":
99
+ graph = load_graph(args.path)
100
+ _emit({"valid": True, "graph": graph.id, "nodes": sorted(graph.nodes)})
101
+ return 0
102
+ if args.command == "route":
103
+ graph = load_graph(args.graph)
104
+ state = RouteState(args.stage, frozenset(args.artifact), _flags(args.flag))
105
+ if args.trail:
106
+ decision = record_route(__import__("specjam.graph_engine", fromlist=["TrailStore"]).TrailStore(args.trail), args.run_id, graph, state)
107
+ else:
108
+ from .graph_engine import route
109
+ decision = route(graph, state)
110
+ _emit(decision.to_dict())
111
+ return 0 if decision.may_advance or not decision.blocked else 2
112
+ if args.command == "rws" and args.rws_command == "validate":
113
+ profile = load_rwsa(args.path)
114
+ _emit({"valid": True, "skill": profile.routing.name, "workflow_steps": len(profile.workflow)})
115
+ return 0
116
+ if args.command == "flow" and args.flow_command == "scaffold":
117
+ path = scaffold_flow(args.target, args.flow, args.slug)
118
+ _emit({"flow": args.flow, "path": str(path)})
119
+ return 0
120
+ return 2
121
+
122
+
123
+ if __name__ == "__main__":
124
+ raise SystemExit(main())